home *** CD-ROM | disk | FTP | other *** search
/ GameStar 2004 April / Gamestar_61_2004-04_dvdb.iso / DVDStar / Editace / hltp.exe / {app} / Applications / QuArK / plugins / mapextruder.py < prev    next >
Text File  |  2004-01-05  |  90KB  |  2,936 lines

  1. # QuArK  -  Quake Army Knife
  2. #
  3. # Copyright (C) 1996-2000 Armin Rigo
  4. # THIS FILE IS PROTECTED BY THE GNU GENERAL PUBLIC LICENCE
  5. # FOUND IN FILE "COPYING.TXT"
  6. #
  7. #
  8. #  This plugin adapted from mapextruder.py, with permission
  9. #    of CryTek Studios.
  10. #
  11. #$Header: /cvsroot/quark/runtime/plugins/mapextruder.py,v 1.16 2003/12/18 21:51:46 peter-b Exp $
  12.  
  13.  
  14. Info = {
  15.    "plug-in":       "Extruder",
  16.    "desc":          "Extrude 2D outlines",
  17.    "date":          "29 Mar 2001",
  18.    "author":        "Crytek Studios, tiglari",
  19.    "author e-mail": "tiglari@planetquake.com",
  20.    "quark":         "Version 6.2"
  21. }
  22.  
  23. from quarkpy.qdictionnary import Strings
  24. import quarkpy.qhandles
  25. import quarkpy.mapduplicator
  26. import quarkpy.maphandles
  27. import quarkpy.maputils
  28. StandardDuplicator = quarkpy.mapduplicator.StandardDuplicator
  29. DuplicatorManager = quarkpy.mapduplicator.DuplicatorManager
  30. RotMat = quarkpy.maputils.ArbRotationMatrix
  31. from quarkpy.qeditor import deg2rad
  32. from quarkpy.maphandles import MapRotateHandle
  33. #from mapmadsel import getstashed
  34. from quarkpy.maputils import *
  35. from tagging import *
  36. #from faceutils import *
  37. from quarkpy.dlgclasses import placepersistent_dialogbox
  38. from quarkpy.qeditor import matrix_rot_z
  39. from quarkpy.qeditor import matrix_rot_y
  40. from quarkpy.qhandles import aligntogrid
  41. from quarkpy import b2utils
  42.  
  43. #
  44. # --- wtf
  45. #
  46. # The ExtruderBuilder or `Flat Extruder' is an overblown
  47. #  duplicator that stores data in a structure of group
  48. #  inside the duplicator, accessed by instances of the
  49. #  ExtruderDupData class.  It's got a 2d window for editing
  50. #  the circumference points, live edit extrusion dialogs,
  51. #  and also path control points that can be individually
  52. #  dragged around.
  53.  
  54. #
  55. #  Sections in this file.
  56. #
  57. #   --Utilities (many copies from elsewhere, needs cleanup)
  58. #   --Duplicator texturing stuff (currently not used)
  59. #   --ExtruderDupData class (partial OOPification of data access)
  60. #   --Path Point stuff (dialogs, RMB menus etc, and a handle)
  61. #   --Actual Shape Drawing (convexification etc. etc.)
  62. #   --Testing Routines (these can probably be dumped)    
  63. #   --Circumference Point management (menus, dialogs handles etc)
  64. #   --The Duplicator at Last
  65. #   --Dissociated Extruder tools (hole-punching, textureing etc)
  66. #   --2d view stuff
  67. #   --Extrusion Dialog stuff
  68.  
  69. #
  70. #          ***** MAJOR SECTION *****
  71. #
  72. # --- Utilities:  many of these are repeats from other
  73. #  places, and ought to be cleaned up.
  74. #
  75.  
  76.  
  77. class AxisHandle(MapRotateHandle):
  78.   "a rotating handle that controls a normalized vector spec"
  79.   
  80.   def __init__(self, center, dup, spec, scale1):
  81.     axis = quarkx.vect(dup[spec])
  82.     MapRotateHandle.__init__(self, center, axis, scale1, quarkpy.qhandles.mapicons[11])
  83.     self.dup = dup
  84.     self.spec = spec
  85.  
  86.   def dragop(self, flags, av):
  87.         new = None
  88.         if av is not None:
  89.             new = self.dup.copy()
  90.             new[self.spec] = av.tuple
  91.         return [self.dup], [new], av
  92.           
  93. def CopyDefaultSpec(source, target, spec, default):
  94.   if source[spec] is not None:
  95.     target[spec] = source[spec]
  96.   else:
  97.     target[spec]=default
  98.  
  99. def AnyIsNone(src, speclist):
  100.   for spec in speclist:
  101.     if src[spec] is None:
  102.       return 1
  103.   return 0
  104.  
  105. def within(containee, container):
  106.   test = containee
  107.   while test is not None:
  108. #    squawk("test: "+test.name)
  109.     if test is container:
  110.       return 1
  111.     test = test.treeparent
  112.   return 0
  113.  
  114. def find_path(source, path):
  115. #  squawk("%s: %s"%(source.name, path))
  116.   if len(path):
  117.     found = source.findname(path[0])
  118.     if found is not None:
  119.       return find_path(found, path[1:])
  120.     return None
  121.   else:
  122.     return source
  123.  
  124. def write3tup(vec):
  125.   return "%.2f %.2f %.2f"%(vec[0], vec[1], vec[2])
  126.  
  127.  
  128.  
  129. #
  130. #  should be in a utility file but isn't
  131. #
  132. def noneint(num):
  133.   if num is None:
  134.     return 0
  135.   else:
  136.     return int(eval(num))
  137.  
  138. #
  139. #          ***** MAJOR SECTION *****
  140. #
  141. #  -- Duplicator texturing stuff, currently not used
  142. #
  143. class TextureDlg (placepersistent_dialogbox):
  144.     #
  145.     # dialog layout
  146.     #
  147.  
  148.     endcolor = AQUA
  149.     size = (120,60)
  150.     dfsep = 0.05
  151.     dlgflags = FWF_KEEPFOCUS | FWF_NOCLOSEBOX | FWF_NOESCCLOSE
  152.  
  153.  
  154.     dlgdef = """
  155.         {
  156.         Style = "9"
  157.         Caption = "Texturing"
  158.  
  159.  
  160.         exit:py = {Txt="" }
  161.     }
  162.     """
  163.     def __init__(self, form, label, editor, dup):
  164.     
  165.         self.dup = dup
  166.         self.editor = editor
  167.         self.src = quarkx.newobj(":")
  168.     
  169.         placepersistent_dialogbox.__init__(self, form, self.src, label,
  170.            exit = quarkpy.qtoolbar.button(
  171.             self.commit,
  172.             "Commit your texture changes and return to regular editing\n (you can't just bail; instead, Undo your changes then commit)\n (if, in spite of my efforts, you find some way to close this box without hitting `commit', you will have made a mess.",
  173.             ico_editor, 0,
  174.             "Commit Texturing"))
  175.  
  176.     def commit(self, dlg):
  177.         self.src = None
  178.         editor=self.editor
  179.         dup = self.dup
  180.         data = ExtruderDupData(dup)
  181.         texobj = dup.findname("texobj:g")
  182.         patches = texobj.findallsubitems("",":b3")
  183.         dict = {}
  184.         if patches:
  185.           for patch in patches:
  186.             name = patch.shortname
  187. #            squawk(name)
  188.             if name != "inner":
  189.               dict[name] = patch.texturename, bezthreepoints(patch, 1)
  190.         else:
  191.           faces = texobj.findallsubitems("",":f")
  192.           for face in faces:
  193.             name = face.shortname
  194.             if name != "inner":
  195.               dict[name] = face.texturename, face.threepoints(4)
  196.         keys = dict.keys()
  197.         texinfo = quarkx.newobj("texinfo:g")
  198.         axes = data.Axes()
  199.         for key in keys:
  200.           side = quarkx.newobj("%s:g"%key)
  201.           side["tex"], threepoints = dict[key]
  202.           p0, p1, p2 = threepoints
  203.           side["p0"] = (data.ProjPos2Tuple(p0,axes))
  204.           side["p1"] = (data.ProjPos2Tuple(p1,axes))
  205.           side["p2"] = (data.ProjPos2Tuple(p2,axes))
  206.           texinfo.appenditem(side)
  207.         undo = quarkx.action()
  208.         undo.exchange(texobj, texinfo)
  209.           
  210.         editor.ok(undo,"commit texturing")
  211.     
  212.         from mapmadsel import Unrestrict
  213.         Unrestrict(editor)
  214.         #
  215.         # non functiona sine timer, who knows why
  216.         #
  217.         def restore(info):
  218.           dup, editor = info
  219.           editor.layout.explorer.sellist = [dup]
  220.         quarkx.settimer(restore,(dup, editor), 20)
  221.         qmacro.dialogbox.close(self, dlg)
  222.  
  223. def tex_pos(self):
  224.   editor = mapeditor()
  225.   if editor is None:
  226.     quarkx.msgbox("Oops, no editor",MT_ERROR,MB_OK)
  227.   dup = editor.layout.explorer.sellist[0]
  228.   if dup is None or dup["macro"] != "dup extruder":
  229.     quarkx.msgbox("Oops, right kind of duplicator not selected",
  230.        MT_ERROR,MB_OK)
  231.   data = ExtruderDupData(dup)
  232.   #
  233.   # make a group of patches or brushes to put the textures on.
  234.   #
  235.   texobj = data.TexObj(editor) 
  236. #  parent = dup.parent
  237.   undo=quarkx.action()
  238.   oldtex = dup.findname("texinfo:g")
  239.   if oldtex is None:
  240.        undo.put(dup, texobj)
  241.   else:
  242.       undo.exchange(oldtex, texobj)
  243.   editor.ok(undo,"texture setup")
  244.   m = qmenu.item("",None)
  245.   m.object=texobj
  246.   from plugins.mapmadsel import RestrictByMe
  247.   RestrictByMe(m)
  248.   TextureDlg(quarkx.clickform, 'corr_tex', editor, dup)
  249.  
  250.  
  251. quarkpy.qmacro.MACRO_tex_pos = tex_pos
  252.  
  253. #
  254. #          ***** MAJOR SECTION *****
  255. #
  256. # -- ExtruderDupData class
  257. #
  258. # a class for defining methods to get at
  259. #   extruder duplicator data; half-assed OOP-ification
  260. #
  261.  
  262.  
  263. class ExtruderDupData:
  264.  
  265.   def __init__(self, dup):
  266.     self.dup = dup
  267.     
  268.   def Path(self):
  269.     return self.dup.findname("spine:g")  
  270.  
  271.   def PathPoints(self):
  272.     return self.dup.findname("spine:g").subitems
  273.     
  274.   def PathLen(self):
  275.     return len(self.PathPoints())
  276.   
  277.   def PathPoint(self, j):
  278.     if j==0:
  279.       return None
  280.     ribs = self.PathPoints()
  281.     if j < len(ribs):
  282.       return ribs[j]
  283.       
  284.   def PathLoc(self, j):
  285.     if j==0:
  286.       return quarkx.vect(0,0,0)
  287.     if type(j) == type(0):
  288.       loc = self.PathPoint(j)
  289.     else:
  290.       loc = j
  291.     if loc is not None:
  292.       loc = quarkx.vect(loc["location"]) 
  293.     return loc
  294.  
  295.   def Org(self):
  296.     dup = self.dup
  297.     if dup.type==":g":
  298.       return quarkx.vect(dup["origin"])
  299.     elif dup.origin is None:
  300.       return quarkx.vect(readNvec(dup["origin"]))
  301.     return dup.origin
  302.  
  303.   def PathPos(self, j):
  304.     pos = self.PathLoc(j)
  305.     if pos is not None:
  306.       pos = self.Org()+pos    
  307. #      pos = self.dup.origin+pos
  308.     return pos
  309.  
  310.   def Circ(self):
  311.    return self.dup.findname("spine:g").subitems[0]
  312.    
  313.   def CircPoints(self):
  314.     spine = self.dup.findname("spine:g")
  315.     return spine.subitems[0].subitems
  316.  
  317.   #
  318.   # get the `bounding square' of the circumference points
  319.   #
  320.   def CircBox(self, xtra=None):
  321.     points = self.CircPoints()
  322.     maxx=maxy=minx=miny=0.0
  323.     for point in points:
  324.       x, y = point["where"]
  325.       if x > maxx: maxx=x
  326.       if x < minx: minx=x
  327.       if y > maxy: maxy=y
  328.       if y < miny: miny=y
  329.     result = []
  330.     if xtra is None:
  331.       xtra=0
  332.     else:
  333.       xtra,=xtra
  334.     maxx, maxy = maxx+xtra, maxy+xtra
  335.     minx, miny = minx-xtra, miny-xtra
  336.     def do(x, y, result=result):
  337.       result.append(quarkx.vect(x, y, 0))
  338.     do(maxx, maxy)
  339.     do(minx, maxy)
  340.     do(minx, miny)
  341.     do(maxx, miny)
  342.     return result
  343.  
  344.   def CircLen(self):
  345.     return len(self.CircPoints())
  346.  
  347.   def CircDec(self, k):
  348.     if k==0:
  349.       return self.CircLen()-1
  350.     else:
  351.       return k-1
  352.  
  353.   def CircCoords(self):
  354.     length = len(self.CircPoints())
  355.     result = []
  356.     for k in range(length):
  357.       point = self.CircPoint(k)
  358.       x, y = point["where"]
  359.       result.append(quarkx.vect(x, y, 0))
  360.     return result
  361.     
  362.    
  363.   def CircPoint(self, k):
  364.     
  365.     points = self.CircPoints()
  366. #    squawk("%d: points %s"%(k,points))
  367. #    squawk("%d, %d"%(k,len(points)))
  368. #    return points [int(math.fmod(k, len(points)))]
  369.     if k >= len(points):
  370.       k=k-len(points)
  371.     if k < 0:
  372.       k=k+len(points)
  373.     return points[k]
  374.  
  375.   def CircAttr(self, k, attr, default=None):
  376.     val = self.CircPoint(k)[attr]
  377.     if val is None:
  378.       val = self.dup[attr]
  379.     if val is None:
  380.       val = default
  381.     return val
  382.  
  383.   def ScaleCircPos(self, k, j=0):
  384.       point = self.CircPoint(k)
  385.       x, y = point["where"]
  386.       scale = get_path_scale(self.dup, j)
  387.       return quarkx.vect(scale*x, scale*y, 0)
  388.  
  389.   def  CircPos(self, k, j=0, planenorm=None):
  390.     pos = self.ScaleCircPos(k, j)
  391.     bevel = get_path_bevel(self.dup, j)
  392.     if bevel:
  393.       prev = self.ScaleCircPos(k-1, j)
  394.       next = self.ScaleCircPos(k+1, j)
  395. #      squawk("prev: %s, pos: %s, next: %s"%(prev, pos, next))
  396.       dir = make_edge(pos-prev, next-pos)
  397. #      squawk("k: %s, dir: %.2f %.2f %.2f"%(k,dir.x,dir.y, dir.z))
  398.       shift = bevel*dir
  399.       pos = pos+shift
  400.     return self.MapCircPos(pos, j, planenorm)
  401.  
  402.   def MapCircPos(self, pos, j=0, planenorm=None):
  403.     x, y, z = pos.tuple
  404.     xaxis, yaxis, zaxis = self.Axes(j)
  405.     org = self.PathPos(j)
  406.     pos = org+x*xaxis+y*yaxis
  407.     if 0<j and j<len(self.PathPoints()):
  408.         if planenorm is None:
  409.           prev_z=self.Zaxis(j-1)
  410.           mat=matrix_rot_u2v(prev_z, (zaxis+prev_z).normalized)
  411.           planenorm = mat*prev_z
  412.         pos = projectpointtoplane(pos, zaxis, org, planenorm)
  413.     return pos
  414.  
  415.   def AdjustPoints(self, points, j, names):
  416.     newpoints = points[:]
  417.     for i in range(len(points)):
  418.       name=names[i]
  419.       if name[:4]=="side":
  420.         k = int(eval(name[5:]))
  421.       elif name[:5]=="inner":
  422.         k = int(eval(names[i-1][5:]))+1
  423.       else: # eeks, it's outer, so bail
  424.         newpoints2 = range(len(points))
  425.         scale = get_path_scale(self.dup, j)
  426.         for i in range(len(points)):
  427.           point = scale*points[i]
  428.           newpoints2[i]=self.MapCircPos(point,j)
  429.         
  430.         return newpoints2
  431.         
  432.  
  433.       newpoints[i]=self.CircPos(k,j)
  434.     return newpoints
  435.  
  436.   def Zaxis(self, j=0):
  437.     dup = self.dup
  438.     if j==0:
  439.       org = self.Org()
  440.     else:
  441.       org = self.PathPos(j)
  442.     diff = self.PathPos(j+1)-org
  443.     return diff.normalized
  444.  
  445.   #
  446.   # Note the cacheing to improve performance.  This means
  447.   #  that if you drag a path control point, you need to
  448.   #  start with a fresh data object.
  449.   #
  450.   def Axes(self, j=0):
  451.      dup = self.dup
  452.      try:
  453.        start = self.cachedj
  454.        xaxis, yaxis, zaxis = self.cachedax
  455.      except (AttributeError):
  456.        start = 1
  457.        zaxis = self.Zaxis()
  458. #       side = quarkx.vect(dup["side"])
  459.        side = orthogonalvect(zaxis)
  460.        yaxis = (side^zaxis).normalized
  461.        xaxis = (zaxis^yaxis)
  462.      last = len(self.PathPoints())-1
  463.  
  464.      for i in range(start,j+1):
  465.          if i==last:
  466.            break
  467.          z2 = self.Zaxis(i)
  468.          if (zaxis-z2):
  469.            mat=matrix_rot_u2v(zaxis, z2)
  470.            xaxis, yaxis, zaxis = mat*xaxis, mat*yaxis, z2
  471.      self.cachedj = j
  472.      self.cachedax = xaxis, yaxis, zaxis
  473.      return xaxis, yaxis, zaxis
  474.  
  475.   def Transform(self, p, j):
  476.     if j==0:
  477.       return p
  478.     org = self.Org()
  479.     xaxis, yaxis, zaxis = self.Axes()
  480.     p = p-org
  481.     x, y, z = p*xaxis, p*yaxis, p*zaxis
  482.     orgj = self.PathPos(j)
  483.     xaxisj, yaxisj, zaxisj = self.Axes(j)
  484.     return orgj+x*xaxisj+y*yaxisj+z*zaxisj
  485.     
  486.  
  487.   def ProjPos2Tuple(self, pos, axes = None):
  488.       if axes is None:
  489.           xaxis, yaxis, zaxis = self.Axes
  490.       else:
  491.           xaxis, yaxis, zaxis = axes
  492.       pos2 = pos-self.Org()
  493.       return pos2*xaxis, pos2*yaxis, pos2*zaxis
  494.  
  495.   def TexObj(self,editor):
  496.     dup=self.dup
  497.     type=dup["type"]
  498.   
  499.     points = self.CircCoords()
  500.     if type == "p":
  501.       list = make_patches(self.dup, points,2)
  502.     if type == "b":
  503.       cycles, names = convexify(points)
  504.       list = make_brushes(dup, cycles, names,2)
  505.     else:
  506.       cycles,names = pipeify(points,self)
  507.       list = make_brushes(dup,cycles,names,2)
  508.  
  509.     
  510.     group = quarkx.newobj("texobj:g")
  511.     for item in list:
  512.       group.appenditem(item)
  513.     return group
  514.       
  515. #
  516. #  Unreconstructed non-methods
  517. #
  518. def get_path_scale(dup, j):
  519.   if j==0:
  520.     return 1.0
  521.   data = ExtruderDupData(dup)
  522.   scale = data.PathPoint(j)["scale"]
  523. #  squawk("scale: %s"%scale)
  524.   if scale is None:
  525.     return 1.0
  526.   else:
  527.     return scale[0]
  528.   
  529. def get_path_bevel(dup, j):
  530.   if j==0:
  531.     return 0.0
  532.   data = ExtruderDupData(dup)
  533.   bevel = data.PathPoint(j)["bevel"]
  534. #  squawk("scale: %s"%scale)
  535.   if bevel is None:
  536.     return 0.0
  537.   else:
  538.     return bevel[0]
  539.   
  540.  
  541. def set_circ_pos(dup, k, pos):
  542.    data = ExtruderDupData(dup)
  543.    if type(k)==type(0):
  544.      point = data.CircPoint(k)
  545.    else:
  546.      point = k
  547.    if point is not None:
  548.      diff = pos-dup.origin
  549.      xaxis, yaxis, zaxis = data.Axes()
  550.      proj = projectpointtoplane(diff, zaxis, dup.origin, zaxis)
  551.      point["where"] = proj*xaxis, proj*yaxis
  552. #     point["where"] = math.atan2(x, y)/deg2rad, abs(proj)
  553.  
  554.   
  555.  
  556. def get_spine(dup):
  557.   return dup.findname("spine:g")
  558.  
  559.  
  560. def set_path_pos(dup, k, pos):
  561.    if k==0:
  562.      return
  563.    if type(k)==type(0):
  564.      spine = dup.findname("spine:g")
  565.      ribs = spine.subitems
  566.      if k < len(ribs):
  567.        point=ribs[k]
  568.      else:
  569.        return
  570.    else:
  571.        point=k
  572.    point["location"] = (pos-dup.origin).tuple
  573.  
  574. #
  575. #          ***** MAJOR SECTION *****
  576. #
  577. # -- Path Point control stuff.
  578. #     the path points control the path of the duplicator;
  579. #     their RMB commands summon up various dialogs
  580. #
  581. # A dialog for setting properties at a Path Point
  582. #   which control the position of the next one (probably
  583. #   a Bad Idea, but I'm leaving it in for now)
  584. #
  585. class SegmentDlg (quarkpy.dlgclasses.LiveEditDlg):
  586.     #
  587.     # dialog layout
  588.     #
  589.  
  590.     endcolor = AQUA
  591.     size = (200,220)
  592.     dfsep = 0.50
  593.  
  594.     dlgdef = """
  595.         {
  596.         Style = "9"
  597.         Caption = "Angle to Next"
  598.  
  599.         sep: = {Typ="S" Txt=" "} 
  600.  
  601.         world: =
  602.         {
  603.         Txt = "Map Coords"
  604.         Typ = "X"
  605.         Hint = "If checked, coordinates are relative to world." $0D "  Otherwise to previous."
  606.         }
  607.  
  608.         sep: = {Typ="S" Txt=" "} 
  609.  
  610.         pitch: = 
  611.         {
  612.         Txt = "Pitch"
  613.         Typ = "EU"
  614.         Hint = "Pitch angle to next, in degrees"
  615.         }
  616.  
  617.         sep: = {Typ="S" Txt=" "} 
  618.  
  619.         yaw: = 
  620.         {
  621.         Txt = "Yaw"
  622.         Typ = "EU"
  623.         Hint = "Yaw angle to next, in degrees"
  624.         }
  625.  
  626.         sep: = {Typ="S" Txt=" "} 
  627.  
  628.          length: = 
  629.          {
  630.          Txt = "Distance"
  631.          Typ = "EU"
  632.          Hint = "Distance to next, in units"
  633.          }
  634.  
  635.  
  636.          sep: = {Typ="S" Txt=" "} 
  637.  
  638.          exit:py = { Txt=""}
  639.     }
  640.     """
  641.  
  642. #
  643. # A dialog for setting properties of a path point
  644. #
  645. class KinkDlg (quarkpy.dlgclasses.LiveEditDlg):
  646.     #
  647.     # dialog layout
  648.     #
  649.  
  650.     endcolor = AQUA
  651.     size = (200,200)
  652.     dfsep = 0.55
  653.  
  654.     dlgdef = """
  655.         {
  656.         Style = "9"
  657.         Caption = "Path Point Properties"
  658.  
  659.         coords: =
  660.         {
  661.         Txt = "Coords"
  662.         Typ = "CL"
  663.         Hint = "What the coords are relative to; world origin," $0D " origin of generator, or previous path-point."
  664.         items = "world" $0D "gen. origin" $0D "previous"
  665.         values = "w" $0D "o" $0D "p"
  666.         }
  667.  
  668.         sep: = {Typ="S" Txt=" "} 
  669.  
  670.         position: =
  671.         {
  672.         Txt = "Position"
  673.         Typ = "EF3"
  674.         Hint = "Position of point, coords relativized as specified above"
  675.         }
  676.         
  677.  
  678.         scale: =
  679.         {
  680.         Txt = "Scale"
  681.         Typ = "EF1"
  682.         Hint = "Size scale at this point, w.r.t. beginning" $0D " Default=1.0"
  683.         }
  684.  
  685.         bevel: =
  686.         {
  687.         Txt = "Bevel"
  688.         Typ = "EF1"
  689.         Hint = "Bevel (w.r.t. beginning, applied after scale)"
  690.         }
  691.  
  692.         sep: = {Typ="S" Txt=" "} 
  693.  
  694.         anchors: =
  695.         {
  696.         Txt = "Show anchors"
  697.         Typ = "X"
  698.         Hint = "If checked, non-draggable `anchor' handles are shown" $0D "that you can tag for attaching things to"
  699.         }
  700.         
  701.          elbow: =
  702.          {
  703.          Txt = "&" Typ = "EF1"
  704.          Hint = "elbowing, mebbe not implemented"
  705.          }
  706.          
  707.         sep: = {Typ="S" Txt=" "} 
  708.  
  709.         exit:py = { Txt=""}
  710.     }
  711.     """
  712.  
  713.  
  714. #
  715. #  The handle
  716. #
  717. class ExtruderPathHandle(quarkpy.maphandles.CenterHandle):
  718.   "A pass-through point for the path of the extruder."
  719.  
  720.   def __init__(self, dup, k):
  721.     self.data = ExtruderDupData(dup)
  722.     pos = self.data.PathPos(k)
  723.     quarkpy.maphandles.CenterHandle.__init__(self, pos, dup, MapColor("Axis"))
  724.     self.k = k
  725.  
  726.   def menu(self, editor, view):
  727.  
  728.     def ins1click(m, dup=self.centerof, k=self.k, editor=editor, data=self.data):
  729.       loc = data.PathLoc(k)
  730. #      squawk("k: %d, loc: %s"%(k,loc))
  731.       new = quarkx.newobj("rib:g")
  732.       spine = get_spine(dup)
  733.       ribs = data.PathPoints()
  734.       undo = quarkx.action()
  735.       if k == len(ribs)-1:
  736.         loc0 = data.PathLoc(k-1)
  737. #        squawk(`loc-loc0`)
  738.         newloc = loc+96.0*(loc-loc0).normalized
  739.         new["location"]=newloc.tuple
  740.         undo.put(spine, new)
  741.       else:
  742.         loc1 = data.PathLoc(k+1)
  743. #        squawk("loc1: %s"%loc1)
  744.         new["location"] = ((loc1+loc)/2).tuple
  745.         undo.put(spine, new, ribs[k+1])
  746.       editor.ok(undo,"Add section")
  747.       editor.layout.explorer.sellist=[dup]
  748.  
  749.     def del1click(m, data=self.data, k=self.k, editor=editor):
  750.       ribs = data.PathPoints()
  751.       if len(ribs)<=2:
  752.         quarkx.msgbox("I'm sorry Dave, I can't let you do that",
  753.           MT_ERROR, MB_OK)
  754.         return
  755.       undo = quarkx.action()
  756.       undo.exchange(ribs[k], None)
  757.       editor.ok(undo, "Delete section")
  758.       editor.layout.explorer.sellist = [data.dup]
  759.  
  760.     def ang1click(m, dup=self.centerof, j=self.k, editor=editor):
  761.  
  762.         #
  763.         # Interestingly, it appears that the data object
  764.         #  can do the work of the usual `pack' object for
  765.         #  LiveEditDialogs.
  766.         #
  767.  
  768.         data = ExtruderDupData(dup)
  769.         data.j = j
  770.  
  771.         def setup(self, data=data):
  772.             src = self.src
  773.             self.data=data
  774.             dup, j = data.dup, data.j
  775.             world = src["world"] = quarkx.setupsubset(SS_MAP, "Options")["WorldSegCoords"]
  776.             if world:
  777.                 xaxis = quarkx.vect(0,-1,0)
  778.                 zaxis = quarkx.vect(1,0,0)
  779.                 yaxis = quarkx.vect(0,0,1)
  780.             else:          
  781.                 xaxis, yaxis, zaxis = data.Axes(j-1)
  782.             curr = data.PathPos(j)
  783.             next = data.PathPos(j+1)
  784.             diff = next-curr
  785.             src["length"] = "%.2f"%abs(diff)
  786.             dir = diff.normalized
  787.             src["pitch"] = "%.2f"%-(math.acos(dir*yaxis)/deg2rad-90)
  788.             src["yaw"] =  "%.2f"%(math.atan2(dir*zaxis, dir*xaxis)/deg2rad-90)
  789.             data.world = world
  790.  
  791.         def action(self, data=data, editor=editor):
  792.             src = self.src
  793.             pitch = eval(src["pitch"])*deg2rad
  794.             yaw = eval(src["yaw"])*deg2rad
  795.             length = eval(src["length"])
  796.             dup, j = data.dup, data.j
  797.             loc = data.PathLoc(j)
  798.             world = src["world"]
  799.             if data.world != world:
  800.                 quarkx.setupsubset(SS_MAP, "Options")["WorldSegCoords"] = world       
  801.                 data.world=world
  802.                 self.setup(self)
  803.                 return
  804.             if world:
  805.                 xaxis = quarkx.vect(0,1,0)
  806.                 zaxis = quarkx.vect(1,0,0)
  807.                 yaxis = quarkx.vect(0,0,1)
  808.             else:
  809.                xaxis, yaxis, zaxis = data.Axes(j-1)
  810.             loc = data.PathLoc(j)
  811.             point = data.PathPoint(j+1)
  812.             diff = math.sin(pitch)*yaxis+math.cos(pitch)*zaxis
  813.     #        diff = quarkx.vect(0, math.cos(pitch), math.sin(pitch))
  814.             mat = RotMat(yaxis, yaw)
  815.             diff = length*(mat*diff)
  816.             newloc = (loc+diff).tuple
  817.             undo = quarkx.action()
  818.             undo.setspec(point, "location", newloc)
  819.             editor.ok(undo, "Move path point")
  820.             editor.layout.explorer.sellist = [data.dup]
  821.  
  822.         SegmentDlg(quarkx.clickform, 'extruderpath', editor, setup, action)
  823.  
  824.  
  825.     def kink1click(m, dup=self.centerof, j=self.k, editor=editor):
  826.       class pack:
  827.         "just a place to stick stuff"
  828.           
  829.       data = ExtruderDupData(dup)
  830.       data.j = j
  831.  
  832.           
  833.       def setup(self, data=data):
  834.         src = self.src
  835.         self.data=data
  836.         dup, j = data.dup, data.j
  837.         coords = src["coords"] = quarkx.setupsubset(SS_MAP, "Options")["KinkCoords"]
  838.         if coords is None:
  839.           coords = src["coords"] = "w"
  840.         pos = data.PathPos(j)
  841.         if coords=="o":
  842.           pos = pos-dup.origin
  843.         elif coords=="p":
  844.           pos = pos-data.PathPos(j-1)
  845.         src["position"] = pos.tuple
  846.         src["scale"] = get_path_scale(dup, j),
  847.         src["bevel"] = get_path_bevel(dup, j),
  848.         src["anchors"] = data.PathPoint(j)["anchors"]
  849.         data.coords = coords        
  850.         
  851.       def action(self, data=data, editor=editor):
  852.         src = self.src
  853.         dup, j = data.dup, data.j
  854.         coords = src["coords"]
  855.         #
  856.         # Change the coordinate system, no map effect
  857.         #
  858.         if coords != data.coords:
  859.             quarkx.setupsubset(SS_MAP, "Options")["KinkCoords"] = coords              
  860.             data.coords = coords
  861.             self.setup(self)
  862.             return
  863.  
  864.         pos = data.PathPos(j)
  865.         newpos = quarkx.vect(src["position"])
  866.         if coords=="o":
  867.           newpos = newpos+dup.origin
  868.         elif coords=="p":
  869.           newpos = newpos + data.PathPos(j-1)
  870.  
  871.         point = data.PathPoint(j)
  872.         undo=quarkx.action()
  873.         if newpos-pos:
  874.           newloc = (newpos-dup.origin).tuple
  875.           undo.setspec(point, "location", newloc)
  876.           editor.ok(undo, "change position")
  877.         elif point["anchors"] != src["anchors"]:
  878.           undo.setspec(point, "anchors", src["anchors"])
  879.           editor.ok(undo, "toggle show anchors")
  880.         else:
  881.           scale, = src["scale"]
  882.           bevel, = src["bevel"]
  883.           point = data.PathPoint(j)
  884.           if scale == 1.0:
  885.             undo.setspec(point,"scale",None)
  886.           else:
  887.             undo.setspec(point, "scale", (scale,))
  888.           if bevel == 0.0:
  889.             undo.setspec(point,"bevel",None)
  890.           else:
  891.             undo.setspec(point, "bevel", (bevel,))
  892.           editor.ok(undo, "set pathpoint scale")
  893.  
  894.         editor.layout.explorer.sellist = [data.dup]
  895.  
  896.       KinkDlg(quarkx.clickform, 'extruderpathkink', editor, setup, action)
  897.  
  898.     def ext1click(m, dup=self.centerof, j=self.k, editor=editor):
  899.       data=ExtruderDupData(dup)
  900.       tagged = m.tagged
  901.       dist = tagged.dist
  902.       norm = tagged.normal
  903.       zaxis = data.Zaxis(j-1)
  904.       pos = projectpointtoplane(data.PathPos(j), zaxis, dist*norm, norm)
  905.       point = data.PathPoint(j)
  906.       undo=quarkx.action()
  907.       undo.setspec(point, "location", (pos-dup.origin).tuple)
  908.       editor.ok(undo,"extend to tagged")
  909.       
  910.       
  911.  
  912.     ang1 = qmenu.item("A&ngle to next", ang1click, "angle to next")
  913.     kink1 = qmenu.item("&Path point properties", kink1click, "scale, etc. of kink")
  914.     ins1 = qmenu.item("&Add a point", ins1click, "add a new control point")
  915.     del1 = qmenu.item("&Delete point", del1click, "remove this control point")
  916.     ext1 = qmenu.item("&Extend to tagged face", ext1click)
  917.     data = ExtruderDupData(self.centerof)
  918.     length = data.PathLen()
  919.     if length<=2:
  920.       del1.state = qmenu.disabled
  921.     if self.k == length-1:
  922.       ang1.state=qmenu.disabled
  923.     if self.k < 1:
  924.       kink1.state = qmenu.disabled
  925.     tagface = gettaggedface(editor)
  926.     if self.k < length-1 or tagface is None:
  927.       ext1.state=qmenu.disabled
  928.     else:
  929.       ext1.tagged = tagface
  930.     return [ins1, del1, ang1, kink1, ext1] + self.OriginItems(editor, view)
  931.  
  932.  
  933.   def drag(self, v1, v2, flags, view):
  934.         delta = v2-v1
  935.         dup, j = self.centerof, self.k
  936.         data = ExtruderDupData(dup)
  937.         pos0 = data.PathPos(j)
  938.         if flags&MB_CTRL:
  939.             newpos = aligntogrid(pos0+delta,1)
  940.         else:
  941.             delta = quarkpy.qhandles.aligntogrid(delta,1)
  942.             newpos = pos0+delta
  943.         if delta or (flags&MB_REDIMAGE):
  944.             new = self.centerof.copy()
  945.             set_path_pos(new, j, newpos)
  946.             new = [new]
  947.         else:
  948.             new = None
  949.         return [self.centerof], new
  950.  
  951. #
  952. #          ***** MAJOR SECTION *****
  953. #
  954. #  -- Actual Shape Drawing
  955.  
  956. #
  957. #  -- code for splitting a possibly concave polygon into convex ones.
  958. #
  959.  
  960. def find_cut(input, i, prev, curr):
  961.   found = None
  962.   curr = curr.normalized
  963.   for j in range(len(input)):
  964.     if j==i or j==i+1: continue
  965.     test = input[j]-input[i]
  966.     if concavity(prev, test): continue
  967.     diff = test.normalized*curr
  968.     if found is None or found[1]<diff:
  969.       found = (j, diff)
  970. #  if found is None:
  971. #    squawk("None found")
  972.   return found[0]
  973.  
  974.  
  975. from quarkpy.qeditor import matrix_rot_z
  976. twister = matrix_rot_z(90*deg2rad)
  977.  
  978. #
  979. # detects a concavity in a counter-clockwise traversal
  980. #  of a polygon
  981. #
  982. def concavity(vec1, vec2):
  983.   axis = twister*vec1
  984. #  squawk("axis: %s, vec2: %s"%(axis, vec2))
  985.   if axis*vec2 <= 0:
  986.     return 1
  987.   return 0
  988.  
  989.  
  990. #
  991. # Split up a possibly concave poly into convex ones
  992. #
  993. # The idea of this one is: cruise around the border until you
  994. #  hit a concave angle.  If you don't you're done & return the
  995. #  cycle.
  996. # Otherwise check all the other vertices & find the whose angle
  997. #  is the smallest one greater than that angle.  Split the border
  998. #  into 2 cycles between the concavity and this new point
  999. #  & return the union of the two cycles.
  1000. #
  1001. # Some of the code in here is for supporting currently
  1002. #  inoperative texturing mechanisms, the idea is that the
  1003. #  names argument would keep track of what sides the edges
  1004. #  come from, and so can be used to control texturing.
  1005. #  Problematic so disabled.
  1006. #
  1007.  
  1008. def convexify(input, names=None):
  1009.     length = len(input)
  1010.     if names==None:
  1011.         names = range(length)
  1012.         for i in names[1:]:
  1013.             names[i] = "side %d"%(i-1)
  1014.         names[0] = "side %d"%(length-1)
  1015.         return convexify(input, names)
  1016. #    squawk("convexifying %s"%input)
  1017.  
  1018. #    squawk(`names`)
  1019.     cycle = input[:]
  1020.     cycle.append(input[0])
  1021.     prev_vect = input[0]-input[length-1]
  1022.  
  1023.     for i in range(length-1):
  1024. #        prev_vect = cycle[i]-cycle[i-1]
  1025.         curr_vect = cycle[i+1]-cycle[i]
  1026.         if concavity(prev_vect, curr_vect):
  1027.             j = find_cut(input, i, prev_vect, curr_vect)
  1028.             pi,pj = decodeName(names[i]), decodeName(names[j])
  1029.             if i<j:
  1030. #                squawk('names: '+`names`)
  1031.                 half1, names1 = convexify(cycle[i:j+1],["inner%d"%pi]+names[i+1:j+1])
  1032.                 half2, names2 = convexify(input[:i+1]+input[j:],names[:i+1]+["inner%d"%pj]+names[j+1:])
  1033. #                squawk('%d<%s: %s'%(i,j,names1+names2))
  1034.                 return half1+half2, names1+names2
  1035.             else:
  1036.                 half1, names1 =  convexify(input[j:i+1],["inner%d"%pj]+names[j+1:i+1])
  1037.                 half2, names2 =  convexify(input[i:]+input[:j+1],["inner%d"%pi]+names[i+1:]+names[:j+1])
  1038. #                squawk('else: '+`names1+names2`)
  1039.                 return half1+half2, names1+names2
  1040.   
  1041.         prev_vect=curr_vect
  1042. #    squawk(`names`)
  1043.     return [input], [names]
  1044.  
  1045. def convexify_nums(input, nums=None):
  1046.   nums = range(len(input))
  1047.   return convexify(input, nums)
  1048. #  squawk("convexifying %s"%input)
  1049.   cycle = input[:]
  1050.   cycle.append(input[0])
  1051.   prev_vect = input[0]-input[length-1]
  1052.  
  1053.   for i in range(length-1):
  1054.     curr_vect = cycle[i+1]-cycle[i]
  1055.     if concavity(prev_vect, curr_vect):
  1056.       j = find_cut(input, i, prev_vect, curr_vect)
  1057.       if i<j:
  1058.         half1, nums1 = convexify(cycle[i:j+1],nums[i:j+1])
  1059.         half2, nums2 = convexify(input[:i+1]+input[j:],nums[:i+1]+nums[j:])
  1060.         return half1+half2, nums1+nums2
  1061.       else:
  1062.         half1, nums1 =  convexify(input[j:i+1],nums[j:i+1])
  1063.         half2, nums2 =  convexify(input[i:]+input[:j+1],nums[i:]+nums[:j+1])
  1064.         return half1+half2, nums1+nums2
  1065.     prev_vect=curr_vect
  1066.  
  1067.   return [input], [nums]
  1068.  
  1069.  
  1070. #
  1071. # This one is supposed to make an vector that sticks out from
  1072. #  the angle between to edges for making bevels etc.
  1073. #
  1074. def make_edge(v1, v2):
  1075.   n1, n2 = v1.normalized, v2.normalized
  1076.   perp = matrix_rot_z(-90*deg2rad)*n1
  1077.   if n1-n2:
  1078.     half = (-n1+n2).normalized
  1079. #    half = math.fabs(half*perp)*half
  1080.     half = half/math.fabs(half*perp)
  1081.     if concavity(n1, n2):
  1082.       return half
  1083.     return -half
  1084.   return perp
  1085.  
  1086. #
  1087. # Turn the innner rim of points into the outer one,for
  1088. #   punching outer holes
  1089. #
  1090. def outrim(input, data):
  1091.   length = len(input)
  1092.   cycle = input[:]
  1093.   cycle.append(input[0])
  1094.   firstedge = None
  1095.   prev_vect = input[0]-input[length-1]
  1096.   curr_vect = cycle[1]-cycle[0]
  1097.   firstedge = edge = make_edge(prev_vect,curr_vect)
  1098. #  squawk("k: 0, edge: %s"%edge)
  1099.   firstwidth, = data.CircAttr(0,"edge", (8.0,))
  1100.   width = firstwidth
  1101.   output = range(length)
  1102.   names = range(length)
  1103.   for i in range(length):
  1104.     if i < length-1:
  1105.       next_vect=cycle[i+2]-cycle[i+1]
  1106.       nextedge = make_edge(curr_vect,next_vect)      
  1107. #      squawk("k: %s, edge: %s"%(i+1,nextedge))
  1108.       nextwidth, = data.CircAttr(i+1,"edge", (8.0,))
  1109.     else:
  1110.       nextedge=firstedge
  1111.       nextwidth=firstwidth
  1112.     output[i]=cycle[i]+width*edge
  1113.  #   output[i]=[cycle[i],cycle[i+1], cycle[i+1]+nextwidth*nextedge, cycle[i]+width*edge]
  1114.     names[i] = "punch%d"%i
  1115.     prev_vect=curr_vect
  1116.     curr_vect = next_vect
  1117.     edge = nextedge
  1118.     width = nextwidth
  1119.   return output,names
  1120.  
  1121. #
  1122. # Autobox puts a square outer wall around a tunnel of
  1123. #  arbitrary shape.  Output wants a bit of optimization
  1124. #  (but doesn't seem too horrible to me).
  1125. #
  1126. # First we generate a long, concave outline from the circumference
  1127. #  points and their expanded bounding square.  Then this is fed
  1128. #  to convexify
  1129. #
  1130.  
  1131. def autobox(input, data):
  1132.   square = data.CircBox(data.dup["edge"])
  1133.   #
  1134.   # get the closed corner and circumference points.
  1135.   #
  1136.   dist, corner, circ = abs(input[0]-square[0]), 0, 0
  1137.   for i in range(len(square)):
  1138.     for j in range(len(input)):
  1139.       d2 = abs(square[i]-input[j])
  1140.       if d2<dist:
  1141.         dist, corner, circ = d2, i, j
  1142.   #
  1143.   # Now make the path, starting at inside, go out, loop around out
  1144.   # go in, loop around in.
  1145.   #
  1146.   first = input[:circ+1]
  1147.   second = input[circ:]
  1148.   first.reverse()
  1149.   second.reverse()
  1150.   path = square[:corner+1]+first+second+square[corner:]
  1151.  
  1152.   def side(i):
  1153.      return "side %d"%i
  1154.   def outer(i):
  1155.      return "outer %d"%i
  1156.      
  1157.   innernames = map(side, range(len(input)))
  1158.   outernames = map(outer, range(4))
  1159.  
  1160.   firstnames = innernames[:circ+1]
  1161.   secondnames = innernames[circ:]
  1162.   firstnames.reverse()
  1163.   secondnames.reverse()
  1164.   
  1165.   names = outernames[:corner+1]+["inner"]+firstnames[1:]+secondnames+["inner"]+outernames[corner+1:]
  1166.   names = [names[len(names)-1]]+names[:len(names)-1]
  1167.   squawk(`path`)
  1168.   squawk(`names`)
  1169.   
  1170. #  squawk("%d, %d"%(len(path),len(names)))
  1171.   return convexify(path, names)
  1172.   
  1173. #
  1174. # Makes pipes
  1175. #
  1176. # The idea of this one is: cruise around the circumference,
  1177. #  for each side generate a loop that will be turned into
  1178. #  a brush by the same code as makes brushes from the output
  1179. #  of convexify;.
  1180. #
  1181. def pipeify(input, data):
  1182.  
  1183.  
  1184. #  squawk("convexifying %s"%input)
  1185.   length = len(input)
  1186.   cycle = input[:]
  1187.   cycle.append(input[0])
  1188.   firstedge = None
  1189.   prev_vect = input[0]-input[length-1]
  1190.   curr_vect = cycle[1]-cycle[0]
  1191.   firstedge = edge = make_edge(prev_vect,curr_vect)
  1192.   firstwidth, = data.CircAttr(0,"edge", (8.0,))
  1193.   width = firstwidth
  1194.   output = range(length)
  1195.   names = range(length)
  1196.   for i in range(length):
  1197.     if i < length-1:
  1198.       next_vect=cycle[i+2]-cycle[i+1]
  1199.       nextedge = make_edge(curr_vect,next_vect)      
  1200.       nextwidth, = data.CircAttr(i+1,"edge", (8.0,))
  1201.     else:
  1202.       nextedge=firstedge
  1203.       nextwidth=firstwidth
  1204.     output[i]=[cycle[i+1], cycle[i], cycle[i]+width*edge, cycle[i+1]+nextwidth*nextedge]
  1205.  #   output[i]=[cycle[i],cycle[i+1], cycle[i+1]+nextwidth*nextedge, cycle[i]+width*edge]
  1206.     names[i] = ["side %d"%i,"inner","outer %d"%i, "inner"]
  1207.     prev_vect=curr_vect
  1208.     curr_vect = next_vect
  1209.     edge = nextedge
  1210.     width = nextwidth
  1211.   return output,names
  1212.  
  1213. #
  1214. # recovers circumference point indexes from connecting side names
  1215. #
  1216. def decodeName(name):
  1217.     return eval(name[5:])
  1218.  
  1219. #
  1220. # Adjusts circumference points for effects of scale
  1221. #   property of path points
  1222. #
  1223. # In order for it to work, the user of this routine needs
  1224. # to know that the name of an edge names its first vertex
  1225. #
  1226. def adjust_points(data, points, j, names):
  1227.   newpoints = points[:]
  1228.   for i in range(len(points)):
  1229.     name=names[i]
  1230.     if name[:4]=="side":
  1231.       k = int(eval(name[5:]))
  1232.     elif name[:5]=="inner":
  1233.       k = int(eval(names[i-1][5:]))+1
  1234. #    if name[:5]!='outer':
  1235. #       k = decodeName(name);
  1236.     else: # eeks, it's outer, so bail
  1237.       newpoints2 = range(len(points))
  1238.       scale = get_path_scale(data.dup, j)
  1239.       for i in range(len(points)):
  1240.         point = scale*points[i]
  1241.         newpoints2[i]=data.MapCircPos(point,j)
  1242.         
  1243.       return newpoints2
  1244.         
  1245.  
  1246.     newpoints[i]=data.CircPos(k,j)
  1247.   return newpoints
  1248.  
  1249. #
  1250. # Attaches the sides to the brushes
  1251. #
  1252. def attach_sides(data, j, brush, bottom, cycle, names, texpos):
  1253.     xaxis, yaxis, zaxis = axes = data.Axes(j-1)
  1254.     frontcycle=adjust_points(data, cycle, j-1, names)
  1255.     backcycle=adjust_points(data, cycle, j, names) 
  1256.     last = frontcycle[-1]
  1257.     for i in range(len(cycle)):
  1258.         pos = frontcycle[i]
  1259.         pos2 = backcycle[i]
  1260.         new = bottom.copy()
  1261.         short = new.shortname = names[i-1]
  1262.         p0, p1, p2 = bottom.threepoints(0)
  1263.         norm = bottom.normal
  1264.         diff = last-pos
  1265.         if not diff:
  1266.             continue
  1267.         gap = pos2-pos
  1268.         norm = -(gap^diff).normalized
  1269.         new.distortion(norm, pos)
  1270.  
  1271.         if texpos is not None:
  1272.             org = data.PathPos(j-1)
  1273.             name = "%s:g"%new.shortname
  1274.   #          squawk(name)
  1275.             side = texpos.findname(name)
  1276.             def mappoint(p, axes=axes,org=org,new=new):
  1277.                 return restore(p,axes,org)
  1278.             if side is not None:
  1279.                 p0, p1, p2 = tuple(map(mappoint,(side["p0"],side["p1"],side["p2"])))
  1280.                 new.setthreepoints((p0, p1, p2),2)
  1281.                 new["tex"] = side["tex"]
  1282.         if new.shortname[:5]=='inner':
  1283.             new["tex"]=CaulkTexture()
  1284.         brush.appenditem(new)
  1285.         last = pos
  1286.  
  1287. #
  1288. # makes the brushes
  1289. #
  1290. def make_brushes(dup, cycles, names, limit=0):
  1291.     data = ExtruderDupData(dup)
  1292.     texpos = dup.findname("texinfo:g")
  1293. #    debug('brushes '+`texpos`)
  1294.     brushes = []
  1295.     if limit:
  1296.         pathlength=limit
  1297.     else:
  1298.         pathlength = len(data.PathPoints())
  1299.     org = prev_org = data.Org()
  1300.     xaxis, yaxis, prev_z = axes = data.Axes()
  1301.     front = quarkx.newobj("front:f")
  1302.     front.setthreepoints((org, org+xaxis, org+yaxis),0)
  1303.     front["tex"] = dup["tex"]
  1304.     front.setthreepoints((org, org+128*xaxis, org+128*yaxis), 2)
  1305.     for j in range(1, pathlength):
  1306.         group = quarkx.newobj("cor_seg_%d:g"%j)
  1307.         curr_org = data.PathPos(j)
  1308.         extension = abs(curr_org-prev_org)
  1309.         back = front.copy()
  1310.         back.swapsides()
  1311.         back.translate(data.PathPos(j)-org)
  1312.         back.shortname = "back"
  1313.         p0, p1, p2 = back.threepoints(0)
  1314.         if j<pathlength-1:
  1315.             zaxis = data.Zaxis(j)
  1316.             mat=matrix_rot_u2v(prev_z, (zaxis+prev_z).normalized)
  1317.             norm = mat*prev_z
  1318.             back.distortion(norm, p0)
  1319.             prev_z = zaxis
  1320.         else:
  1321.             back.distortion(prev_z,p0)
  1322.         for i in range(len(cycles)):
  1323.             brush = quarkx.newobj("brush:p")
  1324.             backside = back.copy()
  1325.             frontside = front.copy()
  1326.             if j<pathlength-1:
  1327.                 backside["tex"]=CaulkTexture()
  1328.             if j>1:
  1329.                 frontside["tex"]=CaulkTexture()
  1330.             brush.appenditem(backside), brush.appenditem(frontside)
  1331.             attach_sides(data, j, brush, front, cycles[i], names[i], texpos)
  1332.             group.appenditem(brush)
  1333.         org = curr_org
  1334.         front=back.copy()
  1335.         front.shortname = "front"
  1336.         front.swapsides()
  1337.         brushes.append(group)
  1338.     return brushes 
  1339.   
  1340.  
  1341. #
  1342. # Used by make_patches, attach_sides below, rethink prolly called for
  1343. #   the idea is to express the threepoints in the local coord
  1344. #   system for the segment, then project onto the face if there
  1345. #   is one (in case there's been scaling).
  1346. #
  1347. def restore(postuple, axes, org, face=None):
  1348.     x, y, z = postuple
  1349.     pos = x*axes[0]+y*axes[1]+z*axes[2]
  1350.     if face is not None:
  1351.         norm = face.normal
  1352.         return projectpointtoplane(pos+org,norm,face.dist*norm,norm)
  1353.     else:        
  1354.         return pos+org
  1355.  
  1356. def make_patches(dup, points, limit=0, editor=None):
  1357.     #
  1358.     # lots of recomputation here that could be meliorized
  1359.     #
  1360. #    squawk("making")
  1361.     data = ExtruderDupData(dup)
  1362.     prev_axes = axes = data.Axes()
  1363. #    if editor is None:
  1364. #      editor=mapeditor()
  1365. #    if editor is None:
  1366. #      return
  1367.       
  1368.     texpos = dup.findname("texinfo:g")
  1369.     texface = quarkx.newobj(":f")
  1370.     patches = []
  1371.     inverse = dup["inverse"]
  1372.     numpoints = len(points)
  1373.     if limit:
  1374.         pathlength=limit
  1375.     else:
  1376.         pathlength = len(data.PathPoints())
  1377.     prev_org = dup.origin
  1378.     if dup["open"]:
  1379.         dopoints = numpoints-1
  1380.     else:
  1381.         dopoints = numpoints
  1382.     prev_pos = range(numpoints)
  1383.     xaxis, yaxis, prev_z = axes = data.Axes()
  1384.     for k in range(numpoints):
  1385.         prev_pos[k] = prev_org+xaxis*points[k].x+yaxis*points[k].y
  1386.     prev_pos.append(prev_pos[0])
  1387.     #
  1388.     # we look at the end-point of the segment
  1389.     #
  1390.     for j in range(1,pathlength):
  1391.       group = quarkx.newobj("cor_seg_%d:g"%j)
  1392.       curr_org = data.PathPos(j)
  1393.       extension = abs(curr_org-prev_org)
  1394. #      squawk("j: %d; ext: %s"%(j, extension))
  1395.       curr_pos = range(numpoints)
  1396.       xaxis, yaxis, zaxis = axes = data.Axes(j)
  1397.       if j<pathlength-1:
  1398.         mat=matrix_rot_u2v(prev_z, (zaxis+prev_z).normalized)
  1399.         planenorm = mat*prev_z
  1400.       else:
  1401.         planenorm=None
  1402.       for k in range(numpoints):
  1403.           curr_pos[k] = data.CircPos(k, j, planenorm)
  1404.       curr_pos.append(curr_pos[0])
  1405.       for k in range(0, dopoints):
  1406.         pos0 = prev_pos[k]
  1407.         pos1 = prev_pos[k+1]
  1408.         pos0e, pos1e = curr_pos[k], curr_pos[k+1]
  1409.         b2 = quarkx.newobj("side %d:b2"%k)
  1410.         b2.cp = b2utils.interpolateGrid(pos0, pos1, pos0e, pos1e)
  1411.         if texpos is not None:
  1412. #          squawk(`k`)
  1413. #          side = texpos.subitem[k]
  1414. #          squawk(`side`)
  1415.           side = texpos.findname("side %d:g"%k)
  1416.           p0 = restore(side["p0"],prev_axes,prev_org)
  1417.           p1 = restore(side["p1"],prev_axes,prev_org)
  1418.           p2 = restore(side["p2"],prev_axes,prev_org)
  1419. #          setthreepointspatch(b3, (p0, p1, p2))
  1420. #          b3.texturename=side["tex"]
  1421.           texface.setthreepoints((p0, p1, p2),1)
  1422.           texface.texturename=side["tex"]
  1423.           texface.setthreepoints((p0, p1, p2), 2)
  1424.           tex_face2patch(editor,texface,b3)
  1425.  
  1426.         else:
  1427.           b2["tex"] = dup["tex"]
  1428. #        b3.texturename = data.CircAttr(k, "tex")
  1429. #        b3.texturename = texname
  1430.         if not inverse:
  1431.           b2.swapsides()
  1432.         group.appenditem(b2)
  1433.       prev_org = curr_org
  1434.       prev_axes = axes
  1435.       prev_pos = curr_pos
  1436.       prev_z = zaxis
  1437.       patches.append(group)
  1438.     return(patches)  
  1439.  
  1440. #
  1441. #          ***** MAJOR SECTION *****
  1442. #
  1443. # -- Testing routines (prolly dumpable, Dissociate Dup
  1444. #     images seems to write errors to the console)
  1445. #
  1446.  
  1447. def testbrushes2(m):
  1448.   dup = m.o
  1449.   data = ExtruderDupData(dup)
  1450.   points = data.CircCoords()
  1451. #  result, names = convexify(points)
  1452.   result, names = autobox(points, data)
  1453.   prisms = make_brushes(dup, result, names)
  1454.   group = quarkx.newobj("brushes:g")
  1455.   for brush in prisms:
  1456.     group.appenditem(brush)
  1457.   undo = quarkx.action()
  1458.   undo.put(dup.parent, group, dup)
  1459.   editor=mapeditor()
  1460.   editor.ok(undo,"make brushes")
  1461.  
  1462.  
  1463. def subitemsfromlist(list,type):
  1464.   group = quarkx.newobj("group:g")
  1465.   for item in list:
  1466.     group.appenditem(item)
  1467.   return group.findallsubitems("",type)
  1468.  
  1469. def testbrushes(m):
  1470.   dup = m.o
  1471.   data = ExtruderDupData(dup)
  1472.   square = data.CircBox(dup["edge"])
  1473. #  squawk(`square`)
  1474.   points = data.CircCoords()
  1475. #  squawk(`points`)
  1476.   result, names = convexify(points)
  1477.   neggies = make_brushes(dup, result, names)
  1478.   box = make_brushes(dup, [square] , [["outer 0","outer 1","outer 2","outer 3"]]) 
  1479.   from mapcsg import CSGlist
  1480.   sublist = subitemsfromlist(neggies,":p")
  1481.   plist = subitemsfromlist(box,":p")
  1482.   CSGlist(plist, sublist)
  1483.  
  1484.   group = quarkx.newobj("group:g")
  1485.   for brush in box:
  1486.     group.appenditem(brush)
  1487.   undo = quarkx.action()
  1488.   undo.put(dup.parent, group, dup)
  1489.   editor=mapeditor()
  1490.   editor.ok(undo,"make brushes")
  1491.   
  1492. def testpatches(m):
  1493.   dup = m.o
  1494.   data = ExtruderDupData(dup)
  1495.   points = data.CircCoords()
  1496.   patches = make_patches(dup, points)
  1497.   undo = quarkx.action()
  1498.   group = quarkx.newobj("patches:g")
  1499.   for patch in patches:
  1500.     group.appenditem(patch)
  1501.   undo.put(dup.parent, group, dup)
  1502.   editor=mapeditor()
  1503.   editor.ok(undo,"make patches")
  1504.  
  1505. def testpipe(m):
  1506.   dup = m.o
  1507.   data = ExtruderDupData(dup)
  1508.   cycles, names = pipeify(data.CircCoords(), data)
  1509.   brushes = make_brushes(dup, cycles, names)
  1510.   undo = quarkx.action()
  1511.   group = quarkx.newobj("pipe:g")
  1512.   for brush in brushes:
  1513.     group.appenditem(brush)
  1514.   undo.put(dup.parent, group, dup)
  1515.   editor=mapeditor()
  1516.   editor.ok(undo,"make pipe")
  1517.   
  1518. #
  1519. #          ***** MAJOR SECTION *****
  1520. #
  1521. # -- Circumference Point management
  1522. #
  1523.  
  1524. class CoordDlg (quarkpy.dlgclasses.LiveEditDlg):
  1525.     #
  1526.     # dialog layout
  1527.     #
  1528.  
  1529.     endcolor = AQUA
  1530.     size = (180,145)
  1531.     dfsep = 0.35
  1532.  
  1533.     
  1534.     dlgdef = """
  1535.         {
  1536.         Style = "9"
  1537.         Caption = "2d Coordinate Positioning"
  1538.  
  1539.         sep: = {Typ="S" Txt=" "} 
  1540.  
  1541.         coords: = 
  1542.         {
  1543.         Txt = "Coords"
  1544.         Typ = "EQ"
  1545.         Hint = "x, y positions; map units per texture tile"
  1546.         }
  1547.  
  1548.       sep: = {Typ="S" Txt=" "} 
  1549.  
  1550.       edge: =
  1551.       {
  1552.         Txt="edge"
  1553.         Typ="EF1"
  1554.         Hint="length of patch-connecting edge in pipe mode"
  1555.       }
  1556.         sep: = {Typ="S" Txt=" "} 
  1557.  
  1558.  
  1559.         exit:py = {Txt="" }
  1560.     }
  1561.     """
  1562.  
  1563.  
  1564. class ExtruderAnchorHandle(quarkpy.maphandles.CenterHandle):
  1565.   "A circumference point for anchoring things to"
  1566.  
  1567.   hint = "Tag to attaching stuff.|Doesn't drag"
  1568.  
  1569.   def __init__(self, dup, k, j):
  1570. #    squawk("getting pos at: %d, %d"%(k, j))
  1571.     data = ExtruderDupData(dup)
  1572.     pos = data.CircPos(k, j)
  1573. #    squawk("p: %s, %d %d"%(pos, k,j))
  1574.     quarkpy.maphandles.CenterHandle.__init__(self, pos, dup, MapColor("Duplicator"))
  1575.     self.k = k
  1576.     self.j =j
  1577.     self.data = data
  1578.     
  1579.   def drag(self, v1, v2, flags, view):
  1580.      return [self.centerof], None    
  1581.  
  1582.   def menu(self, editor, view):
  1583.      return self.OriginItems(editor, view)[1:]
  1584.  
  1585. class ExtruderCircHandle(quarkpy.maphandles.CenterHandle):
  1586.   "A pass-through point for a extruder circumference"
  1587.  
  1588.   def __init__(self, dup, k):
  1589.     data = ExtruderDupData(dup)
  1590.     pos = data.CircPos(k)
  1591.     quarkpy.maphandles.CenterHandle.__init__(self, pos, dup, MapColor("Duplicator"))
  1592.     self.k = k
  1593.     self.data = data
  1594.  
  1595.   def menu(self, editor, view):
  1596.  
  1597.         def backbmp1click(m, view=view, form=editor.form):
  1598.             import quarkpy.qbackbmp
  1599.             quarkpy.qbackbmp.BackBmpDlg(form, view)
  1600.  
  1601.         backbmp1 = qmenu.item("Background image...", backbmp1click, "choose background image")
  1602.  
  1603.         def ins1click(m, dup=self.centerof, k=self.k, editor=editor):
  1604.             insert_point(dup, k, editor)
  1605.  
  1606.  
  1607.         def del1click(m, dup=self.centerof, k=self.k, editor=editor):
  1608.             ptlist = ExtruderDupData(dup).CircPoints()
  1609.             if len(ptlist)<4:
  1610.               quarkx.msgbox("I'm sorry Dave, I can't let you do that",
  1611.                MT_ERROR, MB_OK)
  1612.               return
  1613.             undo = quarkx.action()
  1614.             undo.exchange(ptlist[k],None)
  1615.             undo.ok(editor.Root, "delete point")
  1616.             editor.layout.explorer.sellist = [dup]
  1617.  
  1618.  
  1619.         def coords(data=self.data, k=self.k):
  1620.           x, y = data.CircPoint(k)["where"]
  1621.           return "loc: %s, %s"%(x, y)
  1622.         
  1623.         def setcoords1click(m, dup=self.centerof, k=self.k, editor=editor):
  1624. #          quarkx.msgbox("Not yet implemented",2,4)
  1625.  
  1626.           class pack:
  1627.             "just a place to stick stuff"
  1628.           
  1629.           pack.dup, pack.k = dup, k
  1630.           
  1631.           def setup(self, pack=pack):
  1632.             src = self.src
  1633.             self.pack = pack
  1634.             dup, k = pack.dup, pack.k
  1635.             data = ExtruderDupData(dup)
  1636.             coords = data.CircPoint(k)["where"]
  1637.             self.src["coords"] = "%.2f %.2f"%coords
  1638.             data.edge = self.src["edge"] = data.CircAttr(k, "edge")
  1639.                         
  1640.           def action(self, pack=pack, editor=editor):
  1641.             src = self.src
  1642.             dup, k = pack.dup, pack.k
  1643.             new = dup.copy()
  1644.             newdata = ExtruderDupData(new)
  1645. #            set_circ_pos(new, k, src["coords"])
  1646.             point = newdata.CircPoint(k)
  1647.             point["where"] = read2vec(src["coords"])
  1648.             edge = src["edge"]
  1649.             if edge == new["edge"]:
  1650.               point["edge"] = None
  1651.             else:
  1652.               point["edge"] = edge
  1653.             
  1654.             undo=quarkx.action()
  1655.             undo.exchange(dup, new)
  1656.             editor.ok(undo, "set coords")
  1657.             self.pack.dup = new
  1658.             editor.layout.explorer.sellist = [new]
  1659.             
  1660.           CoordDlg(quarkx.clickform, 'coord2d', editor, setup, action)
  1661.  
  1662.  
  1663.  
  1664.         ins1 = quarkpy.qmenu.item("&Add a point", ins1click, "add a new control point")
  1665.         del1 = quarkpy.qmenu.item("&Delete point", del1click, "remove this control point")
  1666.         loc1 = quarkpy.qmenu.item(coords(), None, "Coords relative to object center")
  1667.         set1 = quarkpy.qmenu.item("&Coords, etc. ",setcoords1click,"|2d coords, edge thickness in pipe mode")
  1668.         return [ins1, del1, set1, backbmp1] + self.OriginItems(editor, view)
  1669.  
  1670.   def drag(self, v1, v2, flags, view):
  1671.         delta = v2-v1
  1672.         data = ExtruderDupData(self.centerof)
  1673.         k = self.k
  1674.         pos0 = data.CircPos(k)
  1675.         if flags&MB_CTRL:
  1676.             newpos = aligntogrid(pos0+delta,1)
  1677.         else:
  1678.             delta = quarkpy.qhandles.aligntogrid(delta, 1)
  1679.             newpos = pos0+delta
  1680.         if delta or (flags&MB_REDIMAGE):
  1681.             new = data.dup.copy()
  1682.             set_circ_pos(new, k, newpos)                    
  1683.             new = [new]
  1684.         else:
  1685.             new = None
  1686.         return [data.dup], new
  1687.  
  1688. def insert_point(dup, k, editor=None):
  1689.             data = ExtruderDupData(dup)
  1690.             point = data.CircPoint(k)
  1691.             ptlist = data.CircPoints()
  1692.             if k < len(ptlist)-1:
  1693.               point2 = data.CircPoint(k+1)
  1694.             else:
  1695.               point2 = data.CircPoint(0)
  1696. #            points = dup.findname("lathpts:g")
  1697.             ang, rad = point["where"]
  1698.             ang2, rad2 = point2["where"]
  1699.             newang, newrad = (ang+ang2)/2, (rad+rad2)/2
  1700.             new = point.copy()
  1701.             new["where"] = newang, newrad
  1702.             #
  1703.             # This option when we're inserting into a new thing
  1704.             #
  1705.             if editor is None:
  1706.  #             spine = dup.findname("spine:g")
  1707.  #             spine.subitems[0].insertitem(k, new)
  1708.               data.Circ().insertitem(k, new)
  1709.               return
  1710.             undo = quarkx.action()
  1711.             undo.put(data.Circ(), new, point2)
  1712.             undo.ok(editor.Root, "add a point")
  1713.             editor.layout.explorer.sellist = [dup]
  1714.  
  1715. class TexDlg (quarkpy.dlgclasses.LiveEditDlg):
  1716.     #
  1717.     # dialog layout
  1718.     #
  1719.  
  1720.     endcolor = AQUA
  1721.     size = (200,220)
  1722.     dfsep = 0.30
  1723.     dlgflags = FWF_KEEPFOCUS
  1724.  
  1725.     dlgdef = """
  1726.         {
  1727.         Style = "9"
  1728.         Caption = "Choose Texture for Panel"
  1729.  
  1730.         sep: = {Typ="S" Txt=" "} 
  1731.  
  1732.  
  1733.          texture: = 
  1734.          {
  1735.          Txt = "texture"
  1736.          Typ = "ET"
  1737.          Hint = "Name of Texture"
  1738.          GameCfg = "DD-1"
  1739.          }
  1740.           outer: = 
  1741.           {
  1742.           Txt = "outer"
  1743.           Typ = "ET"
  1744.           Hint = "Name of Texture for outer surface in pipe mode (texture used if this is empty)"
  1745.           GameCfg = "DD-1"
  1746.           }
  1747.   
  1748.  
  1749.          sep: = {Typ="S" Txt=" "} 
  1750.  
  1751.          exit:py = {Txt="" }
  1752.     }
  1753.     """
  1754.  
  1755.  
  1756. class TweenHandle(quarkpy.maphandles.EdgeHandle):
  1757.     "handle between two passthru's, for adding a new one"
  1758.     undomsg = "add point"
  1759.     hint = "drag to add point"
  1760.  
  1761.     def __init__(self, dup, k, vtx1, vtx2):
  1762.         pos = (vtx2+vtx1)/2
  1763.         quarkpy.qhandles.GenericHandle.__init__(self, pos)
  1764.         self.vtx1, self.vtx2 = vtx1, vtx2
  1765.         cur = CR_CROSSH
  1766. #        cur.pos = pos
  1767.         self.cursor = cur
  1768.         self.pos = pos
  1769.         self.dup, self.k = dup, k
  1770.  
  1771.     def drag(self, v1, v2, flags, view):
  1772.         delta = v2-v1
  1773.         dup = self.dup
  1774.         data = ExtruderDupData(dup)
  1775.         k = self.k
  1776.         pos0 = self.pos
  1777.         if flags&MB_CTRL:
  1778.             g1 = grid[1]
  1779.         else:
  1780.             delta = quarkpy.qhandles.aligntogrid(delta, 0)
  1781.             g1 = 0
  1782.         if delta or (flags&MB_REDIMAGE):
  1783.             new = dup.copy()
  1784. #            if k == 0:
  1785. #              k = len(data.CircPoints())-1
  1786. #              k = k-1
  1787.             insert_point(new, k)
  1788.             set_circ_pos(new, k, pos0+delta)                    
  1789.             new = [new]
  1790.         else:
  1791.             new = None
  1792.         return [dup], new
  1793.  
  1794.  
  1795.  
  1796.     def menu(self, editor, view):
  1797.         self.click(editor)
  1798.         editor.layout.clickedview = view
  1799.  
  1800.         data = ExtruderDupData(self.dup)
  1801.  
  1802.         def add1click(m, data=data, k=self.k, editor=editor):
  1803.           if k == 0:
  1804.             k = len(data.CircPoints())-1
  1805.           else:
  1806.             k = k-1
  1807.           insert_point(data.dup, k, editor)
  1808.         
  1809.         def length(data=data, k=self.k):
  1810.           if k == 0:
  1811.             j = len(data.PathPoints())-1
  1812.           else:
  1813.             j = k-1
  1814.           sep = data.CircPos(k)-data.CircPos(j)
  1815.           return "length: %s"%abs(sep)
  1816.         
  1817.         def tex1click(m, data=data, k=self.k, editor=editor):
  1818.  
  1819.           def setup(self, data=data, k=k):
  1820.             src = self.src
  1821.             dup = data.dup
  1822.             k = data.CircDec(k)
  1823.             data.tex = data.CircAttr(k, "tex")
  1824.             src["texture"] = data.tex
  1825.             data.outer = data.CircAttr(k, "outertex")
  1826.             src["outer"] = data.outer
  1827.           
  1828.           def action(self, data=data, k=k, editor=editor):
  1829.             src = self.src
  1830.             tex = src["texture"]
  1831.             outer = src["outer"]
  1832.             if tex!=data.tex or outer!=data.outer:
  1833.               k = data.CircDec(k)
  1834.               undo = quarkx.action()
  1835.               point = data.CircPoint(k)
  1836.               undo.setspec(point, "tex", tex)
  1837.               undo.setspec(point, "outertex", outer)
  1838.               editor.ok(undo,"set texture")
  1839.               data.tex = tex
  1840.               
  1841.  
  1842.  
  1843.           TexDlg(quarkx.clickform, 'corpaneltex', editor, setup, action)
  1844.  
  1845.           point = data.CircPoint(k)
  1846.  
  1847. #          texholder = point.findname("texholder:b")
  1848. #          if texholder is None:
  1849. #            undo=quarkx.action()
  1850. #            texholder.appenditem(quarkpy.mapbtns.newcube(64, data.dup["tex"]))
  1851. #            undo.put(point, texholder)
  1852. #            editor.ok(undo, "setup for texture")
  1853. #          quarkpy.mapbtns.texturebrowser()
  1854.         
  1855.         add1 = qmenu.item("&Add point", add1click, "Add a Point here")
  1856.         tex1 = qmenu.item("&Texture", tex1click, "Choose texture for this panel")
  1857.         length1 = qmenu.item(length(), None, "Length of this side")
  1858.         
  1859.         return [length1, add1] + self.OriginItems(editor, view)
  1860.  
  1861.  
  1862. #
  1863. #          ***** MAJOR SECTION *****
  1864. #
  1865. # -- The Duplicator at Last
  1866. #
  1867.  
  1868. def tagcordup(dup, editor):
  1869.   editor.tagging = Tagging()
  1870.   editor.tagging.taggedcor = dup
  1871.   editor.invalidateviews()
  1872.   
  1873. def gettaggedcordup(editor):
  1874.   try:
  1875.     cor = editor.tagging.taggedcor
  1876.     if checktree(editor.Root, cor):
  1877.       return cor
  1878.   except (AttributeError):
  1879.     return None
  1880.     
  1881.   
  1882. def extrudermenu(o, editor, oldmenu=quarkpy.mapentities.DuplicatorType.menu.im_func):
  1883.   "duplicator entity menu"
  1884.  
  1885.   menu = oldmenu(o, editor)
  1886.   if o["macro"] !="dup extruder":
  1887.     return menu
  1888.  
  1889.   info = o.findname("data:g")
  1890. #  if info is not None and info["_extruder_data"]:
  1891.   from mapmadsel import getstashed
  1892.   data = ExtruderDupData(o)
  1893.  
  1894.   def PunchInnerClick(m,o=o,editor=editor,info=info,data=data, outer=0):
  1895. #      squawk(`data`)
  1896.     from mapcsg import CSG
  1897.     from mapmadsel import getstashed
  1898.     points = data.CircCoords()
  1899.     if outer:
  1900.       points, names = outrim(points,data)
  1901.       cycles, names = convexify(points, names)
  1902.     else:
  1903.       cycles, names = convexify(points)
  1904.     listgroup = make_brushes(o, cycles, names)
  1905.     sublist = []
  1906.     for group in listgroup:
  1907.       for item in group.findallsubitems("",":p"):
  1908.         sublist.append(item)
  1909. #      if outer:
  1910. #        cycles, names = pipeify(points,data)
  1911. #        outers = make_brushes(o,cycles,names)
  1912. #        for brush in outers[0].findallsubitems("",":p"):
  1913. #          sublist.append(brush)
  1914.     marked = getstashed(editor)
  1915.     plist = marked.findallsubitems("",":p")
  1916.     for p in sublist:
  1917.       if p in plist:
  1918.           plist.remove(p)
  1919.     if not plist:
  1920.       return
  1921.     CSG(editor, plist, sublist, "polyhedron subtraction")
  1922.  
  1923.   def PunchOuterClick(m,o=o,editor=editor,info=info,data=data,punch=PunchInnerClick):
  1924.     punch(m,o,editor,info,data,1)
  1925.  
  1926.   punch_inner = qmenu.item("Punch &Inner",PunchInnerClick,"|Subtract the interior of the tunnel from the marked group")
  1927.   punch_outer = qmenu.item("Punch &Outer",PunchOuterClick,"|Subtract the interior and the walls of the corrridor from the marked group.\n  Onely work works for `pipe'-type.")
  1928.   marked = getstashed(editor)
  1929.   if marked is None:
  1930.     punch_inner.state=punch_outer.state=qmenu.disabled
  1931.     punch_inner.hint=punch_inner.hint+"\n\nTo get the item enabled, mark something with RMB|Navigate Tree|<map object>|Mark."
  1932.   if o["type"]!="t":
  1933.     punch_outer_state=qmenu.disabled
  1934.  
  1935.   testconc1 = qmenu.item("test brushes",testbrushes)
  1936.   testpatch1 = qmenu.item("test patches",testpatches)
  1937.   testpipe1 = qmenu.item("test pipe", testpipe)
  1938.   testconc1.o = testpatch1.o = testpipe1.o = o
  1939.  
  1940.   pathextrude = qmenu.item("Path Extrusion", PathExtrudeClick)
  1941.   radextrude = qmenu.item("Radial Extrusion", RadialExtrudeClick)
  1942.   
  1943.   for item in (pathextrude, radextrude):
  1944.     item.data = ExtruderDupData(o)
  1945.     item.editor = editor
  1946.     item.window = quarkx.clickform
  1947.  
  1948.   def tagclick1(m, editor=editor, o=o):
  1949.     tagcordup(o, editor)
  1950.     
  1951.   def cloneclick1(m, editor=editor, o=o):
  1952.     TagPathPoints = ExtruderDupData(gettaggedcordup(editor)).PathPoints()
  1953.     olddata = ExtruderDupData(o)
  1954.     OldPathPoints = olddata.PathPoints()
  1955.     NewPath = quarkx.newobj("spine:g")
  1956.     for point in [OldPathPoints[0]]+TagPathPoints[1:]:
  1957.       NewPath.appenditem(point.copy())
  1958.     undo = quarkx.action()
  1959.     undo.exchange(olddata.Path(), NewPath)
  1960.     editor.ok(undo,"clone path")
  1961.     
  1962.  
  1963.  
  1964.   n2d = qmenu.item("&2d view", editor.layout.new2dclick)
  1965.   tag = qmenu.item("&Tag", tagclick1, "|Tag duplicator for `clone path' operation")
  1966.   tex = qmenu.item("T&exturing", tex_pos)
  1967.   clone = qmenu.item("&Clone path", cloneclick1, "|Copy path information from tagged.")
  1968.   
  1969.   if gettaggedcordup(editor) is None:
  1970.     clone.state = qmenu.disabled
  1971.   
  1972.   n2d.o = o
  1973. #  numen = [n2d, tex, tag, clone]
  1974.   numen = [punch_inner, punch_outer, n2d, tag, clone, pathextrude, radextrude]
  1975.   if MapOption("Developer"):
  1976.     numen = numen + [testconc1, testpatch1, testpipe1]
  1977.   menu[:0] = numen + [qmenu.sep]
  1978.   return menu
  1979.  
  1980. quarkpy.mapentities.DuplicatorType.menu = extrudermenu
  1981.  
  1982. def extruderchandles(dup, editor, view):
  1983.       chandles = []
  1984.       data = ExtruderDupData(dup)
  1985.       length = len(data.CircPoints())
  1986.       p0  = data.CircPos(length-1)
  1987.       for k in range(length):
  1988.            p = data.CircPos(k)
  1989.            twh = TweenHandle(dup, k, p0, p)
  1990.            twh.hint = "len: %s|drag to add point, RMB for more"%abs(p0-p)
  1991.            chandles.append(twh)
  1992.            p0 = p
  1993.            cch = ExtruderCircHandle(dup, k)
  1994.            cch.hint = "pos %d: %s|RMB for remove point"%(k, data.CircPoint(k)["where"])
  1995.            chandles.append(cch)
  1996.       if chandles == []:
  1997.          quarkx.msgbox("Urrk, no circumference points",MT_WARNING, MB_OK)
  1998.          return
  1999.       pathlength = len(data.PathPoints())
  2000.       if view.info is not None and view.info["type"]!="2D":
  2001.         for j in range(1, pathlength):
  2002.           if data.PathPoint(j)["anchors"]:
  2003.             for k in range(length):
  2004.               chandles.append(ExtruderAnchorHandle(dup, k, j))
  2005.       return chandles
  2006.  
  2007.  
  2008. class ExtruderDuplicator(StandardDuplicator):
  2009.  
  2010.   def handles(self, editor, view):
  2011.     scale = view.scale()
  2012.     dup = self.dup
  2013.     data = ExtruderDupData(dup)
  2014.     org = dup.origin
  2015.     if org is None:
  2016.         org = quarkx.vect(readNvec(dup["origin"]))
  2017.     axishandles = []
  2018.     for k in range(1,len(data.PathPoints())):
  2019.       axishandles.append(ExtruderPathHandle(dup, k))
  2020. #    axishandle = ExtruderPathHandle(dup, 1)
  2021.     #
  2022.     # This `side' thing is needed to make the rotation angles
  2023.     #  define anything, maybe there will be twist factors
  2024.     #
  2025. #    sidehandle = AxisHandle(org, dup, "side", scale)
  2026.  
  2027. #    h = axishandles + [sidehandle]
  2028. #    h = axishandles
  2029.  
  2030.     chandles = extruderchandles(dup,editor,view)
  2031.  
  2032.     return axishandles + chandles + DuplicatorManager.handles(self, editor, view)
  2033.  
  2034.   def buildimages(self, singleimage=None):
  2035.     if singleimage is not None and singleimage>0:
  2036.       return []
  2037.  
  2038.     limit=0
  2039.     dup = self.dup
  2040.     data = ExtruderDupData(dup)
  2041.     aux = noneint(dup["auxiliary"])
  2042.     if aux is not None and aux & 1:
  2043. #      squawk("short")
  2044.       limit=2
  2045.     if dup["short"]:
  2046.       limit=2
  2047.     #
  2048.     # For making a brush
  2049.     #
  2050.     points = data.CircCoords()
  2051.     group = quarkx.newobj("content:g")
  2052.     group["_extruder_content"] = "1"
  2053.     if dup["type"]=="b":
  2054.       cycles, nums = convexify(points)
  2055.       stuff = make_brushes(dup, cycles, nums, limit)
  2056.     elif dup["type"]=="t":
  2057.       cycles, names = pipeify(points, data)
  2058.       stuff = make_brushes(dup, cycles, names, limit)
  2059.     elif dup["type"]=="ab":
  2060.       square = data.CircBox(dup["edge"])
  2061.       points = data.CircCoords()
  2062.       result, names = convexify(points)
  2063.       neggies = make_brushes(dup, result, names, limit)
  2064.       box = make_brushes(dup, [square] , [["outer 0","outer 1","outer 2","outer 3"]], limit) 
  2065.       from mapcsg import CSGlist
  2066.       for i in range(len(neggies)):
  2067.         sublist = neggies[i].findallsubitems("",":p")
  2068.         plist = box[i].findallsubitems("",":p")
  2069.         CSGlist(plist, sublist)
  2070.  
  2071. #      sublist = subitemsfromlist(neggies,":p")
  2072. #      plist = subitemsfromlist(box,":p")
  2073. #      CSGlist(plist, sublist)
  2074.       stuff = box
  2075.     #
  2076.     # otherwise ...
  2077.     #
  2078.     else:
  2079.       stuff = make_patches(dup, points, limit)
  2080.     for thing in stuff:
  2081.         group.appenditem(thing)
  2082.     info = quarkx.newobj("data:g")
  2083.     info.copyalldata(dup)
  2084.     info["_extruder_data"]="1"
  2085.     return [group, info]
  2086.  
  2087. #
  2088. # This stuff could have been stuck into n2dfinishdrawing, but I
  2089. #  decided to keep it separate
  2090. #
  2091. def cortagfinishdrawing(editor, view, oldmore=quarkpy.qbaseeditor.BaseEditor.finishdrawing):
  2092.     oldmore(editor, view)
  2093.     dup = gettaggedcordup(editor)
  2094.     if dup is None: return
  2095.     data = ExtruderDupData(dup)
  2096.     cv = view.canvas()
  2097.     cv.pencolor = MapColor("Tag")
  2098.     cv.penstyle = PS_DOT
  2099.     prev_pos = view.proj(dup.origin)
  2100.     for j in range(1, len(data.PathPoints())):
  2101.       pos = view.proj(data.PathPos(j))
  2102.       cv.line(prev_pos, pos)
  2103.       prev_pos = pos
  2104.  
  2105. quarkpy.qbaseeditor.BaseEditor.finishdrawing = cortagfinishdrawing
  2106.  
  2107.  
  2108. #
  2109. #  Register the duplicator
  2110. #
  2111. quarkpy.mapduplicator.DupCodes.update({
  2112.   "dup extruder":     ExtruderDuplicator,
  2113. })
  2114.  
  2115. #
  2116. #          ***** MAJOR SECTION *****
  2117. #
  2118. #  --Dissociated Extruder tools
  2119. #
  2120.  
  2121. def gettexoption():
  2122.   opt = quarkx.setupsubset(SS_MAP, "Options")["cor_tex_lap"]
  2123.   if opt is None:
  2124.     return "track"
  2125.   else:
  2126.     return opt
  2127.     
  2128. def settexoption(opt):
  2129.   if opt == "track":
  2130.     opt = None
  2131.   quarkx.setupsubset(SS_MAP, "Options")["cor_tex_lap"] = opt
  2132.   
  2133. def getTexInfo(dup, object):
  2134.     oldtex = dup.findname("texinfo:g")
  2135.     if oldtex is not None:
  2136.         dup.removeitem(oldtex)
  2137.     data = ExtruderDupData(dup)
  2138.     content = object.findname("content:g")
  2139.     seg2 = content.findname("cor_seg_1:g")
  2140.     faces = seg2.findallsubitems("",":f")
  2141.     dict = {}
  2142.     for face in faces:
  2143.         name = face.shortname
  2144.         if name != "inner":
  2145.             dict[name] = face.texturename, face.threepoints(2)
  2146.     keys = dict.keys()
  2147.     texinfo = quarkx.newobj("texinfo:g")
  2148.     axes = data.Axes()
  2149.     for key in keys:
  2150.         side = quarkx.newobj("%s:g"%key)
  2151.         side["tex"], threepoints = dict[key]
  2152.         p0, p1, p2 = threepoints
  2153. #        debug('three')
  2154.         side["p0"] = (data.ProjPos2Tuple(p0,axes))
  2155. #        debug('proj')
  2156.         side["p1"] = (data.ProjPos2Tuple(p1,axes))
  2157.         side["p2"] = (data.ProjPos2Tuple(p2,axes))
  2158.         texinfo.appenditem(side)
  2159.     dup.appenditem(texinfo)
  2160. #    debug('append')
  2161.     
  2162. def corgroupmenu(o, editor, oldmenu=quarkpy.mapentities.GroupType.menu.im_func):
  2163.   menu = oldmenu(o, editor)
  2164.   info = o.findname("data:g")
  2165.   if info is not None and info["_extruder_data"]:
  2166.     from mapmadsel import getstashed
  2167.     data = ExtruderDupData(info)
  2168.  
  2169.     def PunchInnerClick(m,o=o,editor=editor,info=info,data=data, outer=0):
  2170. #      squawk(`data`)
  2171.       from mapcsg import CSG
  2172.       from mapmadsel import getstashed
  2173.       points = data.CircCoords()
  2174.       if outer:
  2175.         points, names = outrim(points,data)
  2176.         cycles, names = convexify(points, names)
  2177.       else:
  2178.         cycles, names = convexify(points)
  2179.       listgroup = make_brushes(info, cycles, names)
  2180.       sublist = []
  2181.       for group in listgroup:
  2182.         for item in group.findallsubitems("",":p"):
  2183.           sublist.append(item)
  2184. #      if outer:
  2185. #        cycles, names = pipeify(points,data)
  2186. #        outers = make_brushes(info,cycles,names)
  2187. #        for brush in outers[0].findallsubitems("",":p"):
  2188. #          sublist.append(brush)
  2189.       marked = getstashed(editor)
  2190.       plist = marked.findallsubitems("",":p")
  2191.       for p in sublist:
  2192.         if p in plist:
  2193.             plist.remove(p)
  2194.       if not plist:
  2195.         return
  2196.       CSG(editor, plist, sublist, "polyhedron subtraction")
  2197.  
  2198.     def PunchOuterClick(m,o=o,editor=editor,info=info,data=data,punch=PunchInnerClick):
  2199.       punch(m,o,editor,info,data,1)
  2200.       
  2201.     def RevertClick(m,o=o,editor=editor,info=info):
  2202.       dup = quarkx.newobj("Extruder:d")
  2203.       dup.copyalldata(info)
  2204.       getTexInfo(dup,o)
  2205.       undo=quarkx.action()
  2206.       undo.exchange(o,dup)
  2207.       editor.ok(undo,"Revert to Duplicator")
  2208.       
  2209.     def WrapClick(m,o=o,editor=editor,info=info,data=data):
  2210.       from maptagside import projecttexfrom
  2211.       sources = m.sources
  2212.       cont = find_path(o, ["content:g"])
  2213.       undo = quarkx.action()
  2214.       opt = gettexoption()
  2215.       org = data.Org()
  2216.       for j in range(2,data.PathLen()):
  2217.         for source in sources:
  2218.           name = source.name
  2219.           name, ext = name.split(":")
  2220.           if (name[:4]=="side" or name[:5]=="outer"):
  2221.             tex = source.texturename
  2222.             type = data.dup["type"]
  2223.             if type == "p":
  2224.               face = quarkx.newobj(source.shortname+":b3")
  2225.               face["tex"] = source["tex"]
  2226.               
  2227.             else:
  2228.               texp = source.threepoints(2)
  2229.               face = source.copy()
  2230.             if opt == "track":
  2231.               def transform(p, data=data, j=j):
  2232.                 return data.Transform(p,j-1)
  2233.               texp2 = map(transform, texp)
  2234. #            squawk("%s :: %s"%(texp, texp2))
  2235.               face.setthreepoints(texp2,0)
  2236.               face.setthreepoints(texp2,2)
  2237.             p0, p1, p2 = face.threepoints(1)
  2238.             seg = find_path(cont,["cor_seg_%d:g"%j])
  2239. #            squawk(seg.name)
  2240.             targets = seg.findallsubitems(name,":"+ext)
  2241.             for target in targets[:]:
  2242.               face.distortion(target.normal, p0)
  2243.  #             squawk("%s : %s"%(face.normal,target.normal))
  2244.               new = projecttexfrom(face,target)              
  2245.               undo.exchange(target, new)
  2246.  
  2247.       editor.ok(undo,"wrap texture")
  2248.       editor.layout.explorer.sellist = [o]
  2249.       editor.invalidateviews()      
  2250.       
  2251.     def TexOptClick(m):
  2252.       settexoption(m.opt)
  2253.  
  2254.     punch_inner = qmenu.item("Punch &Inner",PunchInnerClick,"|Subtract the interior of the tunnel from the marked group")
  2255.     punch_outer = qmenu.item("Punch &Outer",PunchOuterClick,"|Subtract the interior and the walls of the corrridor from the marked group.\n  Onely work works for `pipe'-type.")
  2256.     marked = getstashed(editor)
  2257.     if marked is None:
  2258.       punch_inner.state=punch_outer.state=qmenu.disabled
  2259.       punch_inner.hint=punch_inner.hint+"\n\nTo get the item enabled, mark something with RMB|Navigate Tree|<map object>|Mark."
  2260.     if info["type"]!="t":
  2261.       punch_outer_state=qmenu.disabled
  2262.     #
  2263.     # Wrapping textures from tagged faces.
  2264.     #
  2265.     seg1 = find_path(o,["content:g", "cor_seg_1:g"])
  2266.     fromtagged = qmenu.item("From &tagged",WrapClick,"|Wrap texture from first segment to others")
  2267.     type = data.dup["type"]
  2268.     if type == "p":
  2269.       import maptagzbezier
  2270.       tagged = gettaggedbzcplist(editor)
  2271.     else:
  2272.       tagged = gettaggedfaces(editor)
  2273.     if tagged is not None:
  2274. #      squawk(seg1.name)
  2275.       for face in tagged[:]:
  2276.         if not within(face, seg1):
  2277.           tagged.remove(face)
  2278.       if tagged == []:
  2279.         tagged = None
  2280.     if tagged is None:
  2281.         fromtagged.state = qmenu.disabled
  2282.         fromtagged.hint = fromtagged.hint+"\n\nFor this menu item to be enabled, you must tag some faces in the first segment of the extruder."
  2283.     else:
  2284.       fromtagged.sources = tagged
  2285.     fromfirst = qmenu.item("From &first",WrapClick,"|Wraps texture from each face of first segment.")
  2286.     if  type == "p":
  2287.       fromfirst.sources = seg1.findallsubitems("",":b3")
  2288.     else:  
  2289.       fromfirst.sources = seg1.findallsubitems("",":f")
  2290.     list = [fromtagged, fromfirst]
  2291.     curr_opt = gettexoption()
  2292.     for (label, opt, hint) in (("track", "track", "|Texture scale is shifted as is to the other segments, tracking direction changes"),
  2293. #                               ("Lapped", "lapped","|Texture is wrapped around corners to other segments"),
  2294.                                ("project", "project","|Texture is projected onto other segments.\n\n (good for some floors.)")):
  2295.       item = qmenu.item(label, TexOptClick, hint)
  2296.       item.opt = opt
  2297.       if opt == curr_opt:
  2298.         item.state = qmenu.checked
  2299.       list.append(item)
  2300.  
  2301.     wrap_texture = qmenu.popup("&Wrap Texture",list) 
  2302.  
  2303.  
  2304.     revert = qmenu.item("Revert to dup",RevertClick,"|Convert extruder group back to duplicator.\n\nThe effects of holes made etc will all be lost.")
  2305.  
  2306.     itemlist = [punch_inner,punch_outer,wrap_texture,revert]
  2307.     item = qmenu.popup("Extruder Stuff",itemlist)
  2308.     menu[:0] = [item,
  2309.                 qmenu.sep]
  2310.   return menu  
  2311.  
  2312. quarkpy.mapentities.GroupType.menu = corgroupmenu
  2313.  
  2314.  
  2315. def cordupmenu(o, editor, oldmenu=quarkpy.mapentities.DuplicatorType.menu.im_func):
  2316.   menu = oldmenu(o, editor)
  2317.   info = o.findname("data:g")
  2318.   if info is not None and info["_extruder_data"]:
  2319.     from mapmadsel import getstashed
  2320.     data = ExtruderDupData(info)
  2321.  
  2322.     def PunchInnerClick(m,o=o,editor=editor,info=info,data=data, outer=0):
  2323. #      squawk(`data`)
  2324.       from mapcsg import CSG
  2325.       from mapmadsel import getstashed
  2326.       points = data.CircCoords()
  2327.       if outer:
  2328.         points, names = outrim(points,data)
  2329.         cycles, names = convexify(points, names)
  2330.       else:
  2331.         cycles, names = convexify(points)
  2332.       listgroup = make_brushes(info, cycles, names)
  2333.       sublist = []
  2334.       for group in listgroup:
  2335.         for item in group.findallsubitems("",":p"):
  2336.           sublist.append(item)
  2337. #      if outer:
  2338. #        cycles, names = pipeify(points,data)
  2339. #        outers = make_brushes(info,cycles,names)
  2340. #        for brush in outers[0].findallsubitems("",":p"):
  2341. #          sublist.append(brush)
  2342.       marked = getstashed(editor)
  2343.       plist = marked.findallsubitems("",":p")
  2344.       for p in sublist:
  2345.         if p in plist:
  2346.             plist.remove(p)
  2347.       if not plist:
  2348.         return
  2349.       CSG(editor, plist, sublist, "polyhedron subtraction")
  2350.  
  2351.     def PunchOuterClick(m,o=o,editor=editor,info=info,data=data,punch=PunchInnerClick):
  2352.       punch(m,o,editor,info,data,1)
  2353.       
  2354.     #
  2355.     # doesn't work, problems with position computations
  2356.     #
  2357.     def RevertClick(m,o=o,editor=editor,info=info):
  2358.       dup = quarkx.newobj("Flat Extruder:d")
  2359.       dup.copyalldata(info)
  2360.       undo=quarkx.action()
  2361.       undo.exchange(o,dup)
  2362.       editor.ok(undo,"Revert to Duplicator")
  2363.       
  2364.     def WrapClick(m,o=o,editor=editor,info=info,data=data):
  2365.       from maptagside import projecttexfrom
  2366.       sources = m.sources
  2367.       cont = find_path(o, ["content:g"])
  2368.       undo = quarkx.action()
  2369.       opt = gettexoption()
  2370.       org = data.Org()
  2371.       for j in range(2,data.PathLen()):
  2372.         for source in sources:
  2373.           name = source.name
  2374.           name, ext = name.split(":")
  2375.           if (name[:4]=="side" or name[:5]=="outer"):
  2376.             tex = source.texturename
  2377.             type = data.dup["type"]
  2378.             if type == "p":
  2379.               face = quarkx.newobj(source.shortname+":b3")
  2380.               face["tex"] = source["tex"]
  2381.               
  2382.             else:
  2383.               texp = source.threepoints(2)
  2384.               face = source.copy()
  2385.             if opt == "track":
  2386.               def transform(p, data=data, j=j):
  2387.                 return data.Transform(p,j-1)
  2388.               texp2 = map(transform, texp)
  2389. #            squawk("%s :: %s"%(texp, texp2))
  2390.               face.setthreepoints(texp2,0)
  2391.               face.setthreepoints(texp2,2)
  2392.             p0, p1, p2 = face.threepoints(1)
  2393.             seg = find_path(cont,["cor_seg_%d:g"%j])
  2394. #            squawk(seg.name)
  2395.             targets = seg.findallsubitems(name,":"+ext)
  2396.             for target in targets[:]:
  2397.               face.distortion(target.normal, p0)
  2398.  #             squawk("%s : %s"%(face.normal,target.normal))
  2399.               new = projecttexfrom(face,target)              
  2400.               undo.exchange(target, new)
  2401.  
  2402.       editor.ok(undo,"wrap texture")
  2403.       editor.layout.explorer.sellist = [o]
  2404.       editor.invalidateviews()      
  2405.       
  2406.     def TexOptClick(m):
  2407.       settexoption(m.opt)
  2408.  
  2409.     punch_inner = qmenu.item("Punch &Inner",PunchInnerClick,"|Subtract the interior of the tunnel from the marked group")
  2410.     punch_outer = qmenu.item("Punch &Outer",PunchOuterClick,"|Subtract the interior and the walls of the corrridor from the marked group.\n  Onely work works for `pipe'-type.")
  2411.     marked = getstashed(editor)
  2412.     if marked is None:
  2413.       punch_inner.state=punch_outer.state=qmenu.disabled
  2414.       punch_inner.hint=punch_inner.hint+"\n\nTo get the item enabled, mark something with RMB|Navigate Tree|<map object>|Mark."
  2415.     if info["type"]!="t":
  2416.       punch_outer_state=qmenu.disabled
  2417.     #
  2418.     # Wrapping textures from tagged faces.
  2419.     #
  2420.     seg1 = find_path(o,["content:g", "cor_seg_1:g"])
  2421.     fromtagged = qmenu.item("From &tagged",WrapClick,"|Wrap texture from first segment to others")
  2422.     type = data.dup["type"]
  2423.     if type == "p":
  2424.       import maptagzbezier
  2425.       tagged = gettaggedbzcplist(editor)
  2426.     else:
  2427.       tagged = gettaggedfaces(editor)
  2428.     if tagged is not None:
  2429. #      squawk(seg1.name)
  2430.       for face in tagged[:]:
  2431.         if not within(face, seg1):
  2432.           tagged.remove(face)
  2433.       if tagged == []:
  2434.         tagged = None
  2435.     if tagged is None:
  2436.         fromtagged.state = qmenu.disabled
  2437.         fromtagged.hint = fromtagged.hint+"\n\nFor this menu item to be enabled, you must tag some faces in the first segment of the extruder."
  2438.     else:
  2439.       fromtagged.sources = tagged
  2440.     fromfirst = qmenu.item("From &first",WrapClick,"|Wraps texture from each face of first segment.")
  2441.     if  type == "p":
  2442.       fromfirst.sources = seg1.findallsubitems("",":b3")
  2443.     else:  
  2444.       fromfirst.sources = seg1.findallsubitems("",":f")
  2445.     list = [fromtagged, fromfirst]
  2446.     curr_opt = gettexoption()
  2447.     for (label, opt, hint) in (("track", "track", "|Texture scale is shifted as is to the other segments, tracking direction changes"),
  2448. #                               ("Lapped", "lapped","|Texture is wrapped around corners to other segments"),
  2449.                                ("project", "project","|Texture is projected onto other segments.\n\n (good for some floors.)")):
  2450.       item = qmenu.item(label, TexOptClick, hint)
  2451.       item.opt = opt
  2452.       if opt == curr_opt:
  2453.         item.state = qmenu.checked
  2454.       list.append(item)
  2455.  
  2456.     wrap_texture = qmenu.popup("&Wrap Texture",list) 
  2457.  
  2458.  
  2459.     revert = qmenu.item("Revert",RevertClick,"|Convert extruder group back to duplicator.\n\nThe effects of retexturing, holes made etc will all be lost.")
  2460.  
  2461.     itemlist = [punch_inner,punch_outer, wrap_texture]
  2462.     item = qmenu.popup("Extruder Stuff",itemlist)
  2463.     menu[:0] = [item,
  2464.                 qmenu.sep]
  2465.   return menu  
  2466.  
  2467. quarkpy.mapentities.DuplicatorType.menu = cordupmenu
  2468.  
  2469.  
  2470. #
  2471. #          ***** MAJOR SECTION *****
  2472. #
  2473. # --- 2d view stuff
  2474. #
  2475.  
  2476. def view2ddup(editor, view, dup):
  2477.     "Special code to draw only the circumference handles"
  2478.  
  2479.     def drawdup(view, dup=dup, editor=editor):
  2480.         gs = editor.gridstep
  2481.         nullvect = view.vector('0')
  2482.         zero = view.proj(nullvect)
  2483.         xy = [view.scale()*quarkx.vect(gs,0,0),
  2484.               view.scale()*quarkx.vect(0,gs,0)]
  2485.         view.drawgrid(xy[0],xy[1],  MAROON) #, DG_LINES, 0, quarkx.vect(0,0,0))
  2486.         editor.finishdrawing(view)
  2487.         # end of drawsingleface
  2488.  
  2489.     data = ExtruderDupData(dup)
  2490.     origin = dup.origin
  2491.     if origin is None: return
  2492.     n = -data.Zaxis()
  2493.     if not n: return
  2494.  
  2495.     h = []
  2496.      # add the circ. handles
  2497.     h = extruderchandles(dup,editor,view)
  2498.     
  2499.     view.handles = quarkpy.qhandles.FilterHandles(h, SS_MAP)
  2500.  
  2501.     v = orthogonalvect(n, editor.layout.views[0])
  2502.     view.flags = view.flags &~ (MV_HSCROLLBAR | MV_VSCROLLBAR)
  2503.     view.viewmode = "wire"
  2504.     view.info = {"type": "2D",
  2505.                  "matrix": ~ quarkx.matrix(v, v^n, -n),
  2506.                  "bbox": quarkx.boundingboxof(map(lambda h: h.pos, view.handles)),
  2507.                  "scale": 0.01,
  2508.                  "custom": quarkpy.maphandles.singlefacezoom,
  2509.                  "origin": origin,
  2510.                  "noclick": None,
  2511.                  "mousemode": None }
  2512.     quarkpy.maphandles.singlefacezoom(view, origin)
  2513.     quarkpy.maphandles.singlefaceautozoom(view, dup)
  2514.     #
  2515.     # not sure really where to put this stuff
  2516.     #
  2517.     editor.layout.views.append(view)
  2518. #    editor.layout.oldviews = editor.layout.views
  2519. #   editor.layout.views = [view]
  2520.     editor.layout.new2dview = view  # for later removal in mapmgr
  2521. #    editor.layout.new2dobj = dup
  2522.     dup["_n2d"] = "1"
  2523.     m = qmenu.item("",None)
  2524.     m.object=dup
  2525.     from plugins.mapmadsel import RestrictByMe
  2526.     RestrictByMe(m)
  2527.     editor.setupview(view, drawdup, 0)
  2528.     return 1
  2529.  
  2530.  
  2531. def n2dfinishdrawing(editor, view, oldmore=quarkpy.qbaseeditor.BaseEditor.finishdrawing):
  2532.   "the new finishdrawning routine"
  2533.   oldmore(editor, view)
  2534.   
  2535.   try: # some awkwardness so the drawing code will chuck exceptions
  2536.     yes = 0
  2537.     if view is editor.layout.new2dview:
  2538.       yes = 1
  2539.   except: pass
  2540.   
  2541.   sel = editor.layout.explorer.sellist
  2542.   if yes: # and sel != [] and sel is list:
  2543.    if len(sel)<1: # or (sel and sel[0]["_n2d"]):
  2544.     dups = editor.Root.findallsubitems("",":d")
  2545.     for item in dups:
  2546.       if item["_n2d"]:
  2547. #        dup = item
  2548.         editor.layout.explorer.sellist = [item]
  2549.         break
  2550.     if sel:
  2551.       return
  2552.    else:
  2553.  
  2554.       
  2555. #    squawk(`sel`)
  2556.     dup = editor.layout.explorer.sellist[0]
  2557.     data = ExtruderDupData(dup)
  2558.     cv = view.canvas()
  2559.     cv.pencolor = MapColor("Tag")
  2560.     
  2561.     xaxis, yaxis, zaxis = data.Axes()
  2562.     points = data.CircPoints()
  2563.     p0 = prev = view.proj(data.CircPos(0))
  2564.     cv.pencolor = MapColor("Tag")
  2565. #    cv.fontname = "Small Fonts"
  2566. #    cv.fontsize = 5
  2567. #    cv.fontcolor = 0x000000
  2568. #    cv.fontname = "MS Serif"
  2569. #    cv.fontbold = 1
  2570. #    cv.textout(p0.x+5, p0.y, "(%s, %s)"%points[0]["where"])
  2571.     for point in points[1:]:
  2572.       x, y = point["where"]
  2573.       loc = dup.origin+x*xaxis+y*yaxis
  2574.       p = view.proj(loc)
  2575.       cv.line(prev, p)
  2576.       prev = p
  2577.  #     cv.textout(p.x+5, p.y, "(%s, %s)"%(x, y))
  2578.     if not dup["open"]:
  2579.       cv.line(p0, p)
  2580.       
  2581.     tagged = gettaggedcordup(editor)
  2582.     if tagged is not None:
  2583.       cv.penstyle = PS_DOT
  2584.       data = ExtruderDupData(tagged)
  2585.       points = data.CircPoints()
  2586.       p0 = prev = view.proj(data.CircPos(0))
  2587.       for point in points[1:]:
  2588.         x, y = point["where"]
  2589.         loc = dup.origin+x*xaxis+y*yaxis
  2590.         p = view.proj(loc)
  2591.         cv.line(prev, p)
  2592.         prev = p
  2593.       if not dup["open"]:
  2594.         cv.line(p0, p)
  2595.       
  2596.  
  2597. quarkpy.qbaseeditor.BaseEditor.finishdrawing = n2dfinishdrawing
  2598.  
  2599. def new2dclick(self, m):
  2600.       n2d = self.new2dwin
  2601.       if n2d is not None:
  2602.         self.new2dwin.close()
  2603.       if n2d is None:
  2604.         #
  2605.         # Set up the floating window
  2606.         #
  2607.         if mapeditor() is not self.editor: return
  2608.         n2d = quarkx.clickform.newfloating(FWF_KEEPFOCUS, "2D Extruder Window")
  2609.         x1,y1,x2,y2 = quarkx.screenrect()
  2610.         n2d.windowrect = (x2-500, y2-500, x2-20, y2-100)
  2611.         n2d.begincolor = GREEN
  2612.         n2d.endcolor = OLIVE
  2613.         n2d.onclose = self.new2dclose
  2614.         #
  2615.         #
  2616.         # ISSUE: is there a better way to pick the thing that's
  2617.         #   being edited
  2618.         #
  2619.         o = self.explorer.uniquesel
  2620.         #
  2621.         #  The 2d window itself
  2622.         #
  2623.         mv = n2d.mainpanel.newmapview()
  2624.         dup = m.o
  2625.         plugins.mapextruder.view2ddup(self.editor, mv, dup)
  2626.         self.new2dwin = n2d
  2627.       n2d.show()
  2628.  
  2629.  
  2630. def new2dclose(self, m):
  2631.       if self.new2dview in self.views:
  2632.         self.views.remove(self.new2dview)
  2633.       self.new2dwin = None
  2634.       del self.new2dview
  2635.  
  2636.       def restore(self):
  2637.         #
  2638.         # Maybe Unrestrict should be moved to a non-plugin
  2639.         #
  2640.         from plugins.mapmadsel import Unrestrict
  2641.         #
  2642.         # This is needed to prevent errors when editor
  2643.         #   is shut down with 2d window open1   
  2644.         #
  2645.         try:
  2646.           Unrestrict(self.editor)
  2647.           self.editor.invalidateviews()
  2648.         except (AttributeError):
  2649.           pass
  2650.  
  2651.       quarkx.settimer(restore,self,100)
  2652.       
  2653. def newclearrefs(self, oldclearrefs = quarkpy.mapmgr.MapLayout.clearrefs.im_func):
  2654.   oldclearrefs(self)
  2655.   self.new2dwin = None
  2656.  
  2657. quarkpy.mapmgr.MapLayout.new2dclick = new2dclick
  2658. quarkpy.mapmgr.MapLayout.new2dclose = new2dclose
  2659. quarkpy.mapmgr.MapLayout.clearrefs = newclearrefs
  2660.  
  2661.  
  2662. #
  2663. #          ***** MAJOR SECTION *****
  2664. #
  2665. #  Extrusion Dialog stuff
  2666. #
  2667.  
  2668. class PathExtrusionDlg(quarkpy.dlgclasses.LiveEditDlg):
  2669.  
  2670.     endcolor = AQUA
  2671.     size = (220,180)
  2672.     dfsep = 0.5
  2673.  
  2674.     dlgdef = """
  2675.  
  2676.       {
  2677.         Style = "9"
  2678.         Caption = "Path Extrusion Parameters"
  2679.         origin: = {Txt="&" Typ = "EF3"
  2680.                    Hint = "Location in map."}
  2681.         segments: = {Txt="&" Typ = "EF1"
  2682.                     Hint = "Number of segments"}
  2683.         seg len: = {Txt="&" Typ = "EF1"
  2684.                     Hint = "length of each segment"}
  2685.         start angle: = {Txt="&" Typ = "EF2"
  2686.                       Hint = "Starting angle; pitch, yaw relative to Eastward axis." $0D " e.g. 0 0 for due East."}
  2687.  
  2688.         turn angle: = {Txt="&" Typ = "EF2"
  2689.                       Hint = "Total turning angle; pitch, yaw relative to starting angle." $0D " e.g. 0 90 for a quarter-circle heading North."}
  2690.  
  2691.         sep: = { Typ="S" Txt = " "}
  2692.  
  2693.         no update: = {Txt="&" Typ = "X"
  2694.                    Hint = "If this is checked, the duplicator is not actualy updated when new data is entered (uncheck to set changes)." $0D " Useful to speed things up with big ones, or to prevent errors during revisions."}
  2695.  
  2696.       }"""
  2697.  
  2698.  
  2699. class RadialExtrusionDlg(quarkpy.dlgclasses.LiveEditDlg):
  2700.  
  2701.     endcolor = AQUA
  2702.     size = (220,200)
  2703.     dfsep = 0.5
  2704.  
  2705.     dlgdef = """
  2706.  
  2707.       {
  2708.         Style = "9"
  2709.         Caption = "Radial Extrusion Parameters"
  2710.  
  2711.         origin: = {Txt="&" Typ = "EF3"
  2712.                    Hint = "Location in map."}
  2713.         segments: = {Txt="&" Typ = "EF1"
  2714.                     Hint = "Number of segments"}
  2715.         center: = {Txt="&" Typ = "EF3"
  2716.                     Hint = "location of center for radial sweep." $0D " e.g. 0 -256 0 (relative to origin of duplicator by default)."}
  2717.         absolute: =  {Txt="&" Typ = "X"
  2718.                    Hint = "If checked, the center is located in absolute map coordinates" $0D " rather than relative to the origin of the duplicator."}
  2719.         normal: = {Txt="&" Typ = "EF3"
  2720.                   Hint = "Normal to the plane of the sweep."}
  2721.         sweep angle: = {Txt="&" Typ = "EF1"
  2722.                       Hint = "Degrees of arc swept out, in degrees." $0D " e.g. 360 for a full circle"}
  2723.  
  2724.         sep: = { Typ = "S" Txt = " "}
  2725.  
  2726.         no update: = {Txt="&" Typ = "X"
  2727.                    Hint = "If this is checked, the duplicator is not actualy updated when new data is entered (uncheck to set changes)." $0D " Useful to speed things up with big ones, or to prevent errors during revisions."}
  2728.       }"""
  2729.  
  2730. def PathExtrudeClick(btn):
  2731.  
  2732.     data = btn.data
  2733.     dup = data.dup
  2734.     
  2735. #    squawk(`data.PathLen()`)
  2736.     if data.PathLen()>2:
  2737.         if dup["turn angle"] is None:
  2738.            ans = quarkx.msgbox("Entering data into this dialog will overwrite previous work on this map object.  Do you wish to procede?",
  2739.                 MT_WARNING, MB_YES|MB_NO)
  2740.            if ans==MR_NO:
  2741.              return
  2742.              
  2743.     def setup(self, data = data):
  2744.         src = self.src
  2745.         self.data = data
  2746.         dup = self.dup = self.data.dup
  2747.         src["origin"] = dup.origin.tuple
  2748.         src["segments"] = data.PathLen()-1,
  2749.         src["seg len"] = abs(data.PathPos(1)-data.Org()),
  2750.         CopyDefaultSpec(dup, src, "start angle", "")
  2751.         CopyDefaultSpec(dup, src, "turn angle", "")
  2752.  
  2753.         
  2754.     def action(self, data=data, editor=btn.editor):
  2755.         quarkx.globalaccept()
  2756.         src = self.src
  2757.         if src["no update"] or AnyIsNone(src,["start angle", "turn angle"]):
  2758.           return
  2759.           
  2760.         segs, = src["segments"]
  2761.         seglen, = src["seg len"]
  2762.         spitch, syaw = src["start angle"]
  2763.         tpitch, tyaw = src["turn angle"]
  2764.         segs = int(segs)
  2765.         spitch, syaw = spitch*deg2rad, syaw*deg2rad
  2766.         dpitch, dyaw = tpitch*deg2rad/(segs-1), tyaw*deg2rad/(segs-1)
  2767.         undo = quarkx.action()
  2768.         dup = data.dup
  2769.         locstr = write3tup(src["origin"])
  2770.         undo.setspec(dup,"origin",locstr)
  2771.         undo.setspec(dup,"start angle", (spitch, syaw))
  2772.         undo.setspec(dup,"turn angle", (tpitch, tyaw))
  2773.         oldpath = data.Path()  # this will be replaced
  2774.         newpath = quarkx.newobj("spine:g")
  2775.         newpath.appenditem(data.Circ().copy())
  2776.         dir = quarkx.vect(1, 0, 0)
  2777.         dir = matrix_rot_z(syaw)*matrix_rot_y(spitch)*dir
  2778.         twist = matrix_rot_z(dyaw)*matrix_rot_y(dpitch)
  2779.         loc = quarkx.vect(0, 0, 0)
  2780.         for i in range(segs):
  2781.             newrib = quarkx.newobj("rib:g")
  2782.             diff = seglen*dir
  2783.             loc = loc + diff
  2784.             newrib["location"] = loc.tuple
  2785.             newpath.appenditem(newrib)
  2786.             dir = twist*dir
  2787.         undo.exchange(oldpath, newpath)
  2788.         self.editor.ok(undo, "Extrude Path")
  2789.         self.editor.layout.explorer.sellist = [dup]
  2790.   
  2791.     try:
  2792.       btn.editor.layout.new2dwin.close()
  2793.     except (AttributeError):
  2794.       pass
  2795.     PathExtrusionDlg(btn.window, 'path_extrusiondlg', btn.editor, setup, action)
  2796.  
  2797.  
  2798. def RadialExtrudeClick(btn):
  2799.  
  2800.     data = btn.data
  2801.  
  2802.     dup = data.dup
  2803.     
  2804. #    squawk(`data.PathLen()`)
  2805.     if data.PathLen()>2:
  2806.         if dup["sweep angle"] is None:
  2807.            ans = quarkx.msgbox("Entering data into this dialog will overwrite previous work on this map object.  Do you wish to procede?",
  2808.                 MT_WARNING, MB_YES|MB_NO)
  2809.            if ans==MR_NO:
  2810.              return
  2811.  
  2812.  
  2813.     def setup(self, data = data):
  2814.         src = self.src
  2815.         self.data = data
  2816.         dup = self.dup = self.data.dup
  2817.         src["origin"] = dup.origin.tuple
  2818.         CopyDefaultSpec(dup, src, "segments",(3,)) 
  2819.         CopyDefaultSpec(dup, src, "center", "")
  2820.         CopyDefaultSpec(dup, src, "normal", (0,0,1))
  2821.         CopyDefaultSpec(dup,src, "sweep angle", "")
  2822.         if dup["absolute"] is not None:
  2823.           src["absolute"] = "1"
  2824.  
  2825.     
  2826.     def action(self, data=data, editor=btn.editor):
  2827.         quarkx.globalaccept()
  2828.         src = self.src
  2829.         if src["no update"] or AnyIsNone(src, ["segments", "center", "sweep angle"]):
  2830.           return
  2831.         segs, = src["segments"]
  2832.         segs = int(segs)
  2833.         sweep, = src["sweep angle"]
  2834.         normal = quarkx.vect(src["normal"])
  2835.         turn = sweep*deg2rad/segs
  2836.         center = src["center"]
  2837.         center = quarkx.vect(center)
  2838.         rel = src["absolute"]
  2839.         if rel is None:
  2840.           rel=1
  2841.         else:
  2842.           rel=0
  2843.         rot = ArbRotationMatrix(normal, turn)
  2844.         halfrot = ArbRotationMatrix(normal, turn*0.5)
  2845.         undo=quarkx.action()
  2846.         dup = data.dup
  2847.         oldpath = data.Path()  # this will be replaced
  2848.         newpath = quarkx.newobj("spine:g")
  2849.         newpath.appenditem(data.Circ().copy())
  2850.         org = data.Org()
  2851.         if rel:
  2852.             center = org+center
  2853.         loc = org-center
  2854.         shift = center-org
  2855.         for i in range(segs):
  2856.               rimpoint = halfrot*loc
  2857.               rimnorm, locnorm = rimpoint.normalized, loc.normalized
  2858.               point = projectpointtoplane(rimpoint,rimnorm,loc,locnorm)
  2859.               newrib = quarkx.newobj("rib:g")
  2860.               newrib["location"] = (point+shift).tuple
  2861.               newpath.appenditem(newrib)
  2862.               loc = rot*loc
  2863.         newrib = quarkx.newobj("rib:g")
  2864.         newrib["location"] = (loc+shift).tuple
  2865.         newpath.appenditem(newrib)
  2866.         undo.exchange(oldpath, newpath)
  2867.         side = (center-data.Org()).normalized
  2868.         locstr = write3tup(src["origin"])
  2869.         undo.setspec(dup,"origin",locstr)
  2870.         undo.setspec(dup, "side", side.tuple)
  2871.         undo.setspec(dup,"center", src["center"])
  2872.         undo.setspec(dup,"segments", src["segments"])
  2873.         undo.setspec(dup,"normal", src["normal"])
  2874.         undo.setspec(dup,"sweep angle", src["sweep angle"])
  2875.         if rel:
  2876.            undo.setspec(dup,"absolute","")
  2877.         else:
  2878.            undo.setspec(dup,"absolute","1")
  2879.         self.editor.ok(undo, "Extrude Path")
  2880.         self.editor.layout.explorer.sellist = [dup]
  2881.           
  2882.     try:
  2883.       btn.editor.layout.new2dwin.close()
  2884.     except (AttributeError):
  2885.       pass
  2886.     RadialExtrusionDlg(btn.window, 'radial_extrusiondlg', btn.editor, setup, action)
  2887.             
  2888.  
  2889. #
  2890. # not used for the moment, might be resuscitated in an
  2891. #   objectification
  2892. #
  2893. def ExtrudeClick(btn):
  2894.   apply(btn.dlg, (btn.window, 'extrude', btn.editor, btn.olist))
  2895.   btn.editor.layout.new2dwin.close()
  2896.  
  2897.  
  2898. #$Log: mapextruder.py,v $
  2899. #Revision 1.16  2003/12/18 21:51:46  peter-b
  2900. #Removed reliance on external string library from Python scripts (second try ;-)
  2901. #
  2902. #Revision 1.15  2003/09/18 02:55:16  cdunde
  2903. #to fix dialog sep
  2904. #
  2905. #Revision 1.14  2002/05/18 22:38:31  tiglari
  2906. #remove debug statement
  2907. #
  2908. #Revision 1.13  2001/07/09 09:49:41  tiglari
  2909. #eliminate sidehandle in favor of using qedtor.orthogonalvect()
  2910. #
  2911. #Revision 1.12  2001/07/08 00:27:35  tiglari
  2912. #'short' specific; fix angle-to-next and path-point dialog bugs
  2913. #
  2914. #Revision 1.11  2001/06/17 21:10:57  tiglari
  2915. #fix button captions
  2916. #
  2917. #Revision 1.10  2001/06/17 02:25:39  tiglari
  2918. #revert to dup change
  2919. #
  2920. #Revision 1.9  2001/05/22 22:14:38  tiglari
  2921. #texture info built on reversion to dup from dissociated group.  Alignment still wonky
  2922. #
  2923. #Revision 1.8  2001/05/13 02:59:48  tiglari
  2924. #caulk hidden joins
  2925. #
  2926. #Revision 1.7  2001/05/06 10:16:32  tiglari
  2927. #changes to get revert to dup working, hole punching on dup RMB
  2928. # as well as former-dup-group RMB
  2929. #
  2930. #Revision 1.6  2001/05/05 10:02:11  tiglari
  2931. #patch mode supported for extruder
  2932. #
  2933. #Revision 1.5  2001/05/04 23:07:31  tiglari
  2934. #fix log
  2935. #
  2936.